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