use crate::prelude::*;
use actix_web::http::header::ContentDisposition;
use actix_web::{error, web, Error, HttpRequest, HttpResponse};
use csv::WriterBuilder;
use sea_orm::DatabaseConnection;
use serde_derive::{Deserialize, Serialize};
use std::fmt;
use tera::Context;
use super::helpers::{add_default_context_with_session, SearchParams};
use super::{add_auth_context, render_template, validate_sort_by, ListQuery, RoutePrelude};
use crate::admin_prelude;
use crate::ActixAdminModel;
use crate::ActixAdminNotification;
use crate::ActixAdminViewModel;
use crate::ActixAdminViewModelTrait;
use actix_session::Session;
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub enum SortOrder {
Asc,
Desc,
}
impl fmt::Display for SortOrder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
SortOrder::Asc => write!(f, "Asc"),
SortOrder::Desc => write!(f, "Desc"),
}
}
}
pub fn replace_regex(view_model: &ActixAdminViewModel, models: &mut [ActixAdminModel]) {
for field in view_model
.fields
.iter()
.filter(|f| f.list_regex_mask.is_some())
{
let regex = field.list_regex_mask.as_ref().unwrap();
for model in models.iter_mut() {
if let Some(value) = model.values.get_mut(&field.field_name) {
*value = regex.replace_all(value, "****").to_string();
}
}
}
}
pub async fn export_csv<E: ActixAdminViewModelTrait>(
session: Session,
req: HttpRequest,
data: web::Data<ActixAdmin>,
db: web::Data<DatabaseConnection>,
) -> Result<HttpResponse, Error> {
let actix_admin = &data.into_inner();
let ctx = admin_prelude!(&session, &req, actix_admin, RoutePrelude::export(), E);
let query = ListQuery::from_query(req.query_string(), ctx.view_model);
validate_sort_by(ctx.view_model, &query.sort_by)?;
let params = query.to_view_model_params(ctx.tenant_ref, false);
let entities = match E::list(&db, ¶ms).await {
Ok(res) => {
let mut entities = res.1;
replace_regex(ctx.view_model, &mut entities);
entities
}
Err(_) => Vec::new(),
};
let mut writer = WriterBuilder::new().from_writer(vec![]);
let mut fields = ctx
.view_model
.fields
.iter()
.map(|f| f.field_name.clone())
.collect::<Vec<_>>();
fields.insert(0, ctx.view_model.primary_key.clone());
writer
.write_record(&fields)
.map_err(error::ErrorInternalServerError)?;
for entity in entities {
let mut values = vec![entity.primary_key.unwrap_or_default()];
for field in ctx.view_model.fields {
let value = entity
.values
.get(&field.field_name)
.cloned()
.unwrap_or_default();
values.push(
entity
.fk_values
.get(&field.field_name)
.cloned()
.unwrap_or(value),
);
}
writer
.write_record(&values)
.map_err(error::ErrorInternalServerError)?;
}
let body = writer
.into_inner()
.map_err(error::ErrorInternalServerError)?;
Ok(HttpResponse::Ok()
.content_type("text/csv")
.insert_header(ContentDisposition::attachment("export.csv"))
.body(body))
}
pub async fn list<E: ActixAdminViewModelTrait>(
session: Session,
req: HttpRequest,
data: web::Data<ActixAdmin>,
db: web::Data<DatabaseConnection>,
) -> Result<HttpResponse, Error> {
let actix_admin = &data.into_inner();
let route_ctx = admin_prelude!(&session, &req, actix_admin, RoutePrelude::view(), E);
let query = ListQuery::from_query(req.query_string(), route_ctx.view_model);
validate_sort_by(route_ctx.view_model, &query.sort_by)?;
let mut ctx = Context::new();
add_auth_context(&session, actix_admin, &mut ctx);
let vm_params = query.to_view_model_params(route_ctx.tenant_ref, true);
let search_params = SearchParams::from_list_query(&query);
let (num_pages, mut entities) = match E::list(&db, &vm_params).await {
Ok(res) => res,
Err(e) => {
ctx.insert("entities", &Vec::<ActixAdminModel>::new());
ctx.insert("num_pages", &0);
ctx.insert("min_show_page", &1);
ctx.insert("max_show_page", &1);
ctx.insert("page", &1);
ctx.insert("notifications", &[ActixAdminNotification::from(e)]);
return Ok(HttpResponse::InternalServerError()
.content_type("text/html")
.body(
render_template(&actix_admin.tera, "list.html", &ctx)
.map_err(error::ErrorInternalServerError)?,
));
}
};
if let Some(field) = route_ctx
.view_model
.fields
.iter()
.find(|f| f.field_name == query.sort_by)
{
if !field.foreign_key.is_empty() {
let asc = matches!(query.sort_order, SortOrder::Asc);
entities.sort_by(|a, b| {
let av = a
.fk_values
.get(&field.field_name)
.cloned()
.unwrap_or_default();
let bv = b
.fk_values
.get(&field.field_name)
.cloned()
.unwrap_or_default();
if asc {
av.cmp(&bv)
} else {
bv.cmp(&av)
}
});
}
}
replace_regex(route_ctx.view_model, &mut entities);
let num_pages = num_pages.unwrap_or(1);
let page = query.page.min(num_pages);
let min_show_page = page.saturating_sub(4).max(1);
let max_show_page = (page + 4).min(num_pages);
add_default_context_with_session(
&mut ctx,
req,
route_ctx.view_model,
route_ctx.entity_name,
actix_admin,
Vec::new(),
&search_params,
Some(&session),
);
ctx.insert("entities", &entities);
ctx.insert("num_pages", &num_pages);
ctx.insert("min_show_page", &min_show_page);
ctx.insert("max_show_page", &max_show_page);
let mut viewmodel_filter = E::get_viewmodel_filter(&db).await;
for f in &query.filters {
if let Some(entry) = viewmodel_filter.get_mut(&f.name) {
entry.value = f.value.clone();
entry.operator = f.operator.clone();
}
}
ctx.insert("viewmodel_filter", &viewmodel_filter);
Ok(HttpResponse::Ok().content_type("text/html").body(
render_template(&actix_admin.tera, "list.html", &ctx)
.map_err(|err| error::ErrorInternalServerError(format!("{err:?}")))?,
))
}