1use crate::view_model::{ActixAdminViewModelFilter, ActixAdminViewModelParams};
2use crate::{ActixAdminError, ActixAdminViewModelField};
3use actix_multipart::{Multipart, MultipartError};
4use actix_web::web::Bytes;
5use async_trait::async_trait;
6use chrono::{NaiveDate, NaiveDateTime};
7use futures_util::stream::StreamExt as _;
8use sea_orm::{DatabaseConnection, EntityTrait};
9use serde_derive::Serialize;
10use std::collections::HashMap;
11use std::fs::File;
12use std::io::Write;
13use std::time::{SystemTime, UNIX_EPOCH};
14
15#[async_trait]
16pub trait ActixAdminModelTrait {
17 async fn list_model(
18 db: &DatabaseConnection,
19 params: &ActixAdminViewModelParams,
20 filter_values: HashMap<String, Option<String>>
21 ) -> Result<(Option<u64>, Vec<ActixAdminModel>), ActixAdminError>;
22 fn get_fields() -> &'static [ActixAdminViewModelField];
23 fn validate_model(model: &mut ActixAdminModel);
24 async fn load_foreign_keys(models: &mut [ActixAdminModel], db: &DatabaseConnection);
25}
26
27pub trait ActixAdminModelValidationTrait<T> {
28 fn validate(_model: &T) -> HashMap<String, String> {
29 return HashMap::new();
30 }
31}
32
33pub struct ActixAdminModelFilter<E: EntityTrait> {
34 pub name: String,
35 pub filter_type: ActixAdminModelFilterType,
36 pub filter: fn(sea_orm::Select<E>, Option<String>) -> sea_orm::Select<E>,
37 pub values: Option<Vec<(String, String)>>,
38 pub foreign_key: Option<String>
39}
40
41#[derive(Clone, Debug, Serialize)]
42pub enum ActixAdminModelFilterType {
43 Text,
44 SelectList,
45 Date,
46 DateTime,
47 Checkbox,
48 TomSelectSearch
49}
50
51#[async_trait]
52pub trait ActixAdminModelFilterTrait<E: EntityTrait> {
53 fn get_filter() -> Vec<ActixAdminModelFilter<E>> {
54 Vec::new()
55 }
56 async fn get_filter_values(_filter: &ActixAdminModelFilter<E>, _db: &DatabaseConnection)-> Option<Vec<(String, String)>> {
57 None
58 }
59}
60
61impl<T: EntityTrait> From<ActixAdminModelFilter<T>> for ActixAdminViewModelFilter {
62 fn from(filter: ActixAdminModelFilter<T>) -> Self {
63 ActixAdminViewModelFilter {
64 name: filter.name,
65 value: None,
66 values: None,
67 filter_type: Some(filter.filter_type),
68 foreign_key: None
69 }
70 }
71}
72
73#[derive(Clone, Debug, Serialize)]
74pub struct ActixAdminModel {
75 pub primary_key: Option<String>,
76 pub values: HashMap<String, String>,
77 pub fk_values: HashMap<String, String>,
78 pub errors: HashMap<String, String>,
79 pub custom_errors: HashMap<String, String>,
80 pub display_name: Option<String>,
81}
82
83impl ActixAdminModel {
84 pub fn create_empty() -> ActixAdminModel {
85 ActixAdminModel {
86 primary_key: None,
87 values: HashMap::new(),
88 errors: HashMap::new(),
89 custom_errors: HashMap::new(),
90 fk_values: HashMap::new(),
91 display_name: None
92 }
93 }
94
95 pub async fn create_from_payload(
96 id: Option<i32>,
97 mut payload: Multipart, file_upload_folder: &str
98 ) -> Result<ActixAdminModel, MultipartError> {
99 let mut hashmap = HashMap::<String, String>::new();
100
101 while let Some(item) = payload.next().await {
102 let mut field = item?;
103
104 let mut binary_data: Vec<Bytes> = Vec::new();
105 while let Some(chunk) = field.next().await {
106 binary_data.push(chunk.unwrap());
107 }
110 let binary_data = binary_data.concat();
111 if field.content_disposition().expect("expected content disposition").get_filename().is_some() {
112 let mut filename = field
113 .content_disposition()
114 .expect("expected content disposition")
115 .get_filename()
116 .unwrap()
117 .to_string();
118
119 let mut file_path = format!("{}/{}", file_upload_folder, filename);
120 let file_exists = std::path::Path::new(&file_path).exists();
121 if file_exists {
123 filename = format!("{}_{}", SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(), filename);
124 file_path = format!("{}/{}", file_upload_folder, filename);
125 }
126
127 let file = File::create(file_path);
128 let _res = file.unwrap().write_all(&binary_data);
129
130 hashmap.insert(
131 field.name().expect("expected file name").to_string(),
132 filename.clone()
133 );
134 } else {
135 let res_string = String::from_utf8(binary_data);
136 if res_string.is_ok() {
137 hashmap.insert(field.name().expect("expected file name").to_string(), res_string.unwrap());
138 }
139 }
140 }
141
142 Ok(ActixAdminModel {
143 primary_key: match id {
144 Some(id) => Some(id.to_string()),
145 None => None
146 },
147 values: hashmap,
148 errors: HashMap::new(),
149 custom_errors: HashMap::new(),
150 fk_values: HashMap::new(),
151 display_name: None
152 })
153 }
154
155 pub fn get_value<T: std::str::FromStr>(
156 &self,
157 key: &str,
158 is_option_or_string: bool,
159 is_allowed_to_be_empty: bool
160 ) -> Result<Option<T>, String> {
161 self.get_value_by_closure(key, is_option_or_string, is_allowed_to_be_empty, |val| val.parse::<T>())
162 }
163
164 pub fn get_datetime(
165 &self,
166 key: &str,
167 is_option_or_string: bool,
168 is_allowed_to_be_empty: bool
169 ) -> Result<Option<NaiveDateTime>, String> {
170 self.get_value_by_closure(key, is_option_or_string, is_allowed_to_be_empty, |val| {
171 NaiveDateTime::parse_from_str(val, "%Y-%m-%dT%H:%M")
172 })
173 }
174
175 pub fn get_date(
176 &self,
177 key: &str,
178 is_option_or_string: bool,
179 is_allowed_to_be_empty: bool
180 ) -> Result<Option<NaiveDate>, String> {
181 self.get_value_by_closure(key, is_option_or_string, is_allowed_to_be_empty, |val| {
182 NaiveDate::parse_from_str(val, "%Y-%m-%d")
183 })
184 }
185
186 pub fn get_bool(&self, key: &str, is_option_or_string: bool, is_allowed_to_be_empty: bool) -> Result<Option<bool>, String> {
187 let val = self.get_value_by_closure(key, is_option_or_string, is_allowed_to_be_empty ,|val| {
188 if !val.is_empty() && (val == "true" || val == "yes") {
189 Ok(true)
190 } else {
191 Ok(false)
192 }
193 });
194 match val {
196 Ok(val) => Ok(val),
197 Err(_) => Ok(Some(false)),
198 }
199 }
200
201 fn get_value_by_closure<T: std::str::FromStr>(
202 &self,
203 key: &str,
204 is_option_or_string: bool,
205 is_allowed_to_be_empty: bool,
206 f: impl Fn(&String) -> Result<T, <T as std::str::FromStr>::Err>,
207 ) -> Result<Option<T>, String> {
208 let value = self.values.get(key);
209
210 let res: Result<Option<T>, String> = match value {
211 Some(val) => {
212 match (val.is_empty(), is_option_or_string, is_allowed_to_be_empty) {
213 (true, true, true) => return Ok(None),
214 (true, true, false) => return Err("Cannot be empty".to_string()),
215 _ => {}
216 };
217
218 let parsed_val = f(val);
219
220 match parsed_val {
221 Ok(val) => Ok(Some(val)),
222 Err(_) => Err("Invalid Value".to_string()),
223 }
224 }
225 _ => {
226 match (is_option_or_string, is_allowed_to_be_empty) {
227 (true, true) => Ok(None),
228 (true, false) => Err("Cannot be empty".to_string()),
229 (false, _) => Err("Invalid Value".to_string()), }
231 }
232 };
233
234 res
235 }
236
237 pub fn has_errors(&self) -> bool {
238 return (&self.errors.len() + &self.custom_errors.len()) != 0 as usize;
239 }
240}