1use crate::prelude::*;
2use actix_web::http::header::ContentDisposition;
3use actix_web::{error, web, Error, HttpRequest, HttpResponse};
4use csv::WriterBuilder;
5use sea_orm::DatabaseConnection;
6use serde_derive::{Deserialize, Serialize};
7use std::fmt;
8use tera::Context;
9
10use super::helpers::{add_default_context_with_session, SearchParams};
11use super::{add_auth_context, render_template, validate_sort_by, ListQuery, RoutePrelude};
12use crate::admin_prelude;
13use crate::ActixAdminModel;
14use crate::ActixAdminNotification;
15use crate::ActixAdminViewModel;
16use crate::ActixAdminViewModelTrait;
17use actix_session::Session;
18
19#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
20pub enum SortOrder {
21 Asc,
22 Desc,
23}
24
25impl fmt::Display for SortOrder {
26 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
27 match self {
28 SortOrder::Asc => write!(f, "Asc"),
29 SortOrder::Desc => write!(f, "Desc"),
30 }
31 }
32}
33
34pub fn replace_regex(view_model: &ActixAdminViewModel, models: &mut [ActixAdminModel]) {
35 for field in view_model
36 .fields
37 .iter()
38 .filter(|f| f.list_regex_mask.is_some())
39 {
40 let regex = field.list_regex_mask.as_ref().unwrap();
41 for model in models.iter_mut() {
42 if let Some(value) = model.values.get_mut(&field.field_name) {
43 *value = regex.replace_all(value, "****").to_string();
44 }
45 }
46 }
47}
48
49pub async fn export_csv<E: ActixAdminViewModelTrait>(
50 session: Session,
51 req: HttpRequest,
52 data: web::Data<ActixAdmin>,
53 db: web::Data<DatabaseConnection>,
54) -> Result<HttpResponse, Error> {
55 let actix_admin = &data.into_inner();
56 let ctx = admin_prelude!(&session, &req, actix_admin, RoutePrelude::export(), E);
57
58 let query = ListQuery::from_query(req.query_string(), ctx.view_model);
59 validate_sort_by(ctx.view_model, &query.sort_by)?;
60
61 let params = query.to_view_model_params(ctx.tenant_ref, false);
62
63 let entities = match E::list(&db, ¶ms).await {
64 Ok(res) => {
65 let mut entities = res.1;
66 replace_regex(ctx.view_model, &mut entities);
67 entities
68 }
69 Err(_) => Vec::new(),
70 };
71
72 let mut writer = WriterBuilder::new().from_writer(vec![]);
73 let mut fields = ctx
74 .view_model
75 .fields
76 .iter()
77 .map(|f| f.field_name.clone())
78 .collect::<Vec<_>>();
79 fields.insert(0, ctx.view_model.primary_key.clone());
80 writer
81 .write_record(&fields)
82 .map_err(error::ErrorInternalServerError)?;
83
84 for entity in entities {
85 let mut values = vec![entity.primary_key.unwrap_or_default()];
86 for field in ctx.view_model.fields {
87 let value = entity
88 .values
89 .get(&field.field_name)
90 .cloned()
91 .unwrap_or_default();
92 values.push(
93 entity
94 .fk_values
95 .get(&field.field_name)
96 .cloned()
97 .unwrap_or(value),
98 );
99 }
100 writer
101 .write_record(&values)
102 .map_err(error::ErrorInternalServerError)?;
103 }
104
105 let body = writer
106 .into_inner()
107 .map_err(error::ErrorInternalServerError)?;
108 Ok(HttpResponse::Ok()
109 .content_type("text/csv")
110 .insert_header(ContentDisposition::attachment("export.csv"))
111 .body(body))
112}
113
114pub async fn list<E: ActixAdminViewModelTrait>(
115 session: Session,
116 req: HttpRequest,
117 data: web::Data<ActixAdmin>,
118 db: web::Data<DatabaseConnection>,
119) -> Result<HttpResponse, Error> {
120 let actix_admin = &data.into_inner();
121 let route_ctx = admin_prelude!(&session, &req, actix_admin, RoutePrelude::view(), E);
122
123 let query = ListQuery::from_query(req.query_string(), route_ctx.view_model);
124 validate_sort_by(route_ctx.view_model, &query.sort_by)?;
125
126 let mut ctx = Context::new();
127 add_auth_context(&session, actix_admin, &mut ctx);
128
129 let vm_params = query.to_view_model_params(route_ctx.tenant_ref, true);
130 let search_params = SearchParams::from_list_query(&query);
131
132 let (num_pages, mut entities) = match E::list(&db, &vm_params).await {
133 Ok(res) => res,
134 Err(e) => {
135 ctx.insert("entities", &Vec::<ActixAdminModel>::new());
136 ctx.insert("num_pages", &0);
137 ctx.insert("min_show_page", &1);
138 ctx.insert("max_show_page", &1);
139 ctx.insert("page", &1);
140 ctx.insert("notifications", &[ActixAdminNotification::from(e)]);
141 return Ok(HttpResponse::InternalServerError()
142 .content_type("text/html")
143 .body(
144 render_template(&actix_admin.tera, "list.html", &ctx)
145 .map_err(error::ErrorInternalServerError)?,
146 ));
147 }
148 };
149
150 if let Some(field) = route_ctx
156 .view_model
157 .fields
158 .iter()
159 .find(|f| f.field_name == query.sort_by)
160 {
161 if !field.foreign_key.is_empty() {
162 let asc = matches!(query.sort_order, SortOrder::Asc);
163 entities.sort_by(|a, b| {
164 let av = a
165 .fk_values
166 .get(&field.field_name)
167 .cloned()
168 .unwrap_or_default();
169 let bv = b
170 .fk_values
171 .get(&field.field_name)
172 .cloned()
173 .unwrap_or_default();
174 if asc {
175 av.cmp(&bv)
176 } else {
177 bv.cmp(&av)
178 }
179 });
180 }
181 }
182
183 replace_regex(route_ctx.view_model, &mut entities);
184 let num_pages = num_pages.unwrap_or(1);
185 let page = query.page.min(num_pages);
186 let min_show_page = page.saturating_sub(4).max(1);
187 let max_show_page = (page + 4).min(num_pages);
188
189 add_default_context_with_session(
190 &mut ctx,
191 req,
192 route_ctx.view_model,
193 route_ctx.entity_name,
194 actix_admin,
195 Vec::new(),
196 &search_params,
197 Some(&session),
198 );
199
200 ctx.insert("entities", &entities);
201 ctx.insert("num_pages", &num_pages);
202 ctx.insert("min_show_page", &min_show_page);
203 ctx.insert("max_show_page", &max_show_page);
204 let mut viewmodel_filter = E::get_viewmodel_filter(&db).await;
205 for f in &query.filters {
209 if let Some(entry) = viewmodel_filter.get_mut(&f.name) {
210 entry.value = f.value.clone();
211 entry.operator = f.operator.clone();
212 }
213 }
214 ctx.insert("viewmodel_filter", &viewmodel_filter);
215
216 Ok(HttpResponse::Ok().content_type("text/html").body(
217 render_template(&actix_admin.tera, "list.html", &ctx)
218 .map_err(|err| error::ErrorInternalServerError(format!("{err:?}")))?,
219 ))
220}