1use async_trait::async_trait;
2use regex::Regex;
3use sea_orm::DatabaseConnection;
4use serde_derive::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7use crate::ActixAdminError;
8use crate::{model::ActixAdminModelFilterType, ActixAdminModel, SortOrder};
9use actix_session::Session;
10use std::convert::From;
11pub struct ActixAdminViewModelParams {
12 pub page: Option<u64>,
13 pub entities_per_page: Option<u64>,
14 pub viewmodel_filter: Vec<ActixAdminViewModelFilter>,
15 pub search: String,
16 pub sort_by: String,
17 pub sort_order: SortOrder,
18 pub tenant_ref: Option<i32>,
19}
20
21pub trait ActixAdminPrimaryKey:
32 serde::de::DeserializeOwned + std::str::FromStr + std::fmt::Display + Clone + 'static
33{
34}
35impl<T> ActixAdminPrimaryKey for T where
36 T: serde::de::DeserializeOwned + std::str::FromStr + std::fmt::Display + Clone + 'static
37{
38}
39
40#[async_trait(?Send)]
41pub trait ActixAdminViewModelTrait {
42 type Id: ActixAdminPrimaryKey;
46
47 async fn list(
48 db: &DatabaseConnection,
49 params: &ActixAdminViewModelParams,
50 ) -> Result<(Option<u64>, Vec<ActixAdminModel>), ActixAdminError>;
51
52 async fn create_entity(
54 db: &DatabaseConnection,
55 model: ActixAdminModel,
56 tenant_ref: Option<i32>,
57 ) -> Result<ActixAdminModel, ActixAdminError>;
58 async fn delete_entity(
59 db: &DatabaseConnection,
60 id: Self::Id,
61 tenant_ref: Option<i32>,
62 ) -> Result<bool, ActixAdminError>;
63
64 async fn delete_entities(
69 db: &DatabaseConnection,
70 ids: &[Self::Id],
71 tenant_ref: Option<i32>,
72 ) -> Result<u64, ActixAdminError> {
73 let mut deleted = 0u64;
74 for id in ids {
75 if Self::delete_entity(db, id.clone(), tenant_ref).await? {
76 deleted += 1;
77 }
78 }
79 Ok(deleted)
80 }
81
82 async fn get_entity(
83 db: &DatabaseConnection,
84 id: Self::Id,
85 tenant_ref: Option<i32>,
86 ) -> Result<ActixAdminModel, ActixAdminError>;
87 async fn edit_entity(
88 db: &DatabaseConnection,
89 id: Self::Id,
90 model: ActixAdminModel,
91 tenant_ref: Option<i32>,
92 ) -> Result<ActixAdminModel, ActixAdminError>;
93 async fn get_select_lists(
94 db: &DatabaseConnection,
95 tenant_ref: Option<i32>,
96 ) -> Result<HashMap<String, Vec<(String, String)>>, ActixAdminError>;
97 async fn get_viewmodel_filter(
98 db: &DatabaseConnection,
99 ) -> HashMap<String, ActixAdminViewModelFilter>;
100 async fn validate_entity(model: &mut ActixAdminModel, db: &DatabaseConnection);
101
102 fn get_entity_name() -> String;
103}
104
105#[derive(Clone, Debug, Serialize)]
109pub struct ActixAdminBulkAction {
110 pub name: String,
112 pub label: String,
114 pub icon: Option<String>,
116 pub confirm: Option<String>,
118}
119
120#[derive(Clone)]
121pub struct ActixAdminViewModel {
122 pub entity_name: String,
123 pub primary_key: String,
124 pub fields: &'static [ActixAdminViewModelField],
125 pub show_search: bool,
126 pub user_can_access: Option<fn(&Session) -> bool>,
130 pub user_can_create: Option<fn(&Session) -> bool>,
133 pub user_can_edit: Option<fn(&Session) -> bool>,
134 pub user_can_delete: Option<fn(&Session) -> bool>,
135 pub user_can_view_details: Option<fn(&Session) -> bool>,
136 pub user_can_export: Option<fn(&Session) -> bool>,
137 pub default_show_aside: bool,
138 pub inline_edit: bool,
139 pub bulk_actions: Vec<ActixAdminBulkAction>,
142}
143
144#[derive(Clone, Debug, Serialize)]
145pub struct ActixAdminViewModelSerializable {
146 pub entity_name: String,
147 pub primary_key: String,
148 pub fields: &'static [ActixAdminViewModelField],
149 pub show_search: bool,
150 pub default_show_aside: bool,
151 pub inline_edit: bool,
152 #[serde(default)]
156 pub can_create: bool,
157 #[serde(default)]
158 pub can_edit: bool,
159 #[serde(default)]
160 pub can_delete: bool,
161 #[serde(default)]
162 pub can_view_details: bool,
163 #[serde(default)]
164 pub can_export: bool,
165 pub bulk_actions: Vec<ActixAdminBulkAction>,
166}
167
168#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
171#[serde(rename_all = "snake_case")]
172pub enum ActixAdminFilterOperator {
173 Equals,
174 NotEquals,
175 Contains,
176 NotContains,
177 GreaterThan,
178 LessThan,
179 GreaterEquals,
180 LessEquals,
181 IsNull,
182 IsNotNull,
183 InList,
184}
185
186impl ActixAdminFilterOperator {
187 pub fn as_str(&self) -> &'static str {
188 match self {
189 Self::Equals => "equals",
190 Self::NotEquals => "not_equals",
191 Self::Contains => "contains",
192 Self::NotContains => "not_contains",
193 Self::GreaterThan => "gt",
194 Self::LessThan => "lt",
195 Self::GreaterEquals => "gte",
196 Self::LessEquals => "lte",
197 Self::IsNull => "is_null",
198 Self::IsNotNull => "is_not_null",
199 Self::InList => "in",
200 }
201 }
202
203 pub fn label(&self) -> &'static str {
204 match self {
205 Self::Equals => "=",
206 Self::NotEquals => "≠",
207 Self::Contains => "contains",
208 Self::NotContains => "does not contain",
209 Self::GreaterThan => ">",
210 Self::LessThan => "<",
211 Self::GreaterEquals => "≥",
212 Self::LessEquals => "≤",
213 Self::IsNull => "is empty",
214 Self::IsNotNull => "is not empty",
215 Self::InList => "in list",
216 }
217 }
218
219 pub fn from_str(s: &str) -> Option<Self> {
223 <Self as std::str::FromStr>::from_str(s).ok()
224 }
225}
226
227impl std::str::FromStr for ActixAdminFilterOperator {
228 type Err = ();
229 fn from_str(s: &str) -> Result<Self, Self::Err> {
230 match s {
231 "equals" | "eq" | "=" => Ok(Self::Equals),
232 "not_equals" | "ne" | "!=" => Ok(Self::NotEquals),
233 "contains" | "like" => Ok(Self::Contains),
234 "not_contains" | "not_like" => Ok(Self::NotContains),
235 "gt" | ">" => Ok(Self::GreaterThan),
236 "lt" | "<" => Ok(Self::LessThan),
237 "gte" | ">=" => Ok(Self::GreaterEquals),
238 "lte" | "<=" => Ok(Self::LessEquals),
239 "is_null" | "empty" => Ok(Self::IsNull),
240 "is_not_null" | "not_empty" => Ok(Self::IsNotNull),
241 "in" | "in_list" => Ok(Self::InList),
242 _ => Err(()),
243 }
244 }
245}
246
247#[derive(Clone, Debug, Serialize)]
248pub struct ActixAdminViewModelFilter {
249 pub name: String,
250 pub value: Option<String>,
251 pub foreign_key: Option<String>,
252 pub values: Option<Vec<(String, String)>>,
253 pub filter_type: Option<ActixAdminModelFilterType>,
254 #[serde(default)]
258 pub operators: Vec<ActixAdminFilterOperator>,
259 #[serde(default)]
261 pub operator: Option<ActixAdminFilterOperator>,
262}
263
264impl ActixAdminViewModelSerializable {
265 pub fn from_view_model(entity: &ActixAdminViewModel) -> Self {
274 ActixAdminViewModelSerializable {
275 entity_name: entity.entity_name.clone(),
276 primary_key: entity.primary_key.clone(),
277 fields: entity.fields,
278 show_search: entity.show_search,
279 default_show_aside: entity.default_show_aside,
280 inline_edit: entity.inline_edit,
281 can_create: false,
282 can_edit: false,
283 can_delete: false,
284 can_view_details: false,
285 can_export: false,
286 bulk_actions: entity.bulk_actions.clone(),
287 }
288 }
289}
290
291impl From<ActixAdminViewModel> for ActixAdminViewModelSerializable {
295 fn from(entity: ActixAdminViewModel) -> Self {
296 Self::from_view_model(&entity)
297 }
298}
299
300#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
301pub enum ActixAdminViewModelFieldType {
302 Number,
303 Text,
304 TextArea,
305 Checkbox,
306 Date,
307 Time,
308 DateTime,
309 SelectList,
310 FileUpload,
311 Html,
314 Url,
316 Email,
318 Image,
321 RichText,
324}
325
326#[derive(Clone, Debug, Serialize, Deserialize)]
327pub struct ActixAdminViewModelField {
328 pub field_name: String,
329 pub html_input_type: String,
330 pub select_list: String,
331 pub dateformat: Option<String>,
332 pub is_option: bool,
333 pub field_type: ActixAdminViewModelFieldType,
334 pub list_sort_position: usize,
335 pub list_hide_column: bool,
336 #[serde(skip_serializing, skip_deserializing)]
337 pub list_regex_mask: Option<Regex>,
338 pub foreign_key: String,
339 pub is_tenant_ref: bool,
340 pub ceil: Option<u8>,
341 pub floor: Option<u8>,
342 pub shorten: Option<u16>,
343 pub use_tom_select_callback: bool,
344 #[serde(default)]
347 pub readonly: bool,
348}
349
350impl ActixAdminViewModelFieldType {
351 #[allow(clippy::too_many_arguments)]
352 pub fn get_field_type(
353 type_path: &str,
354 select_list: String,
355 is_textarea: bool,
356 is_file_upload: bool,
357 is_image: bool,
358 is_html: bool,
359 is_url: bool,
360 is_email: bool,
361 is_wysiwyg: bool,
362 ) -> ActixAdminViewModelFieldType {
363 if !select_list.is_empty() {
364 return ActixAdminViewModelFieldType::SelectList;
365 }
366 if is_image {
367 return ActixAdminViewModelFieldType::Image;
368 }
369 if is_wysiwyg {
370 return ActixAdminViewModelFieldType::RichText;
371 }
372 if is_textarea {
373 return ActixAdminViewModelFieldType::TextArea;
374 }
375 if is_file_upload {
376 return ActixAdminViewModelFieldType::FileUpload;
377 }
378 if is_html {
379 return ActixAdminViewModelFieldType::Html;
380 }
381 if is_url {
382 return ActixAdminViewModelFieldType::Url;
383 }
384 if is_email {
385 return ActixAdminViewModelFieldType::Email;
386 }
387
388 match type_path {
389 "i32" => ActixAdminViewModelFieldType::Number,
390 "i64" => ActixAdminViewModelFieldType::Number,
391 "usize" => ActixAdminViewModelFieldType::Number,
392 "String" => ActixAdminViewModelFieldType::Text,
393 "bool" => ActixAdminViewModelFieldType::Checkbox,
394 "DateTimeWithTimeZone" => ActixAdminViewModelFieldType::DateTime,
395 "DateTime" => ActixAdminViewModelFieldType::DateTime,
396 "Date" => ActixAdminViewModelFieldType::Date,
397 _ => ActixAdminViewModelFieldType::Text,
398 }
399 }
400}