#[cfg(server)]
use super::admin_auth::AdminAuthenticatedUser;
use crate::adapters::{
AdminDatabase, AdminRecord, AdminSite, ColumnInfo, FilterInfo, FilterType, ListResponse,
ModelAdmin,
};
#[cfg(server)]
use crate::core::{AdminDatabaseKey, AdminSiteKey};
#[cfg(server)]
use reinhardt_db::orm::{Filter, FilterCondition, FilterOperator, FilterValue};
#[cfg(server)]
use reinhardt_di::Depends;
use reinhardt_pages::server_fn::{ServerFnError, server_fn};
use std::sync::Arc;
#[cfg(server)]
use super::error::{MapServerFnError, require_object_filters};
#[cfg(server)]
use super::limits::MAX_PAGE_SIZE;
#[cfg(server)]
use super::validation::retain_allowed_fields;
#[cfg(server)]
use crate::server::type_inference::{
get_field_metadata, infer_admin_field_type, infer_filter_type,
};
#[cfg(server)]
use reinhardt_utils::utils_core::text::humanize_field_name;
#[cfg(server)]
fn build_filters(model_admin: &Arc<dyn ModelAdmin>) -> Vec<FilterInfo> {
let table_name = model_admin.table_name();
model_admin
.list_filter()
.iter()
.map(|field| {
let filter_type = get_field_metadata(table_name, field)
.map(|meta| {
let admin_type = infer_admin_field_type(&meta.field_type);
infer_filter_type(&admin_type)
})
.unwrap_or(FilterType::Boolean);
FilterInfo {
field: field.to_string(),
title: humanize_field_name(field),
filter_type,
current_value: None,
}
})
.collect()
}
#[cfg(server)]
fn build_columns(model_admin: &Arc<dyn ModelAdmin>) -> Vec<ColumnInfo> {
model_admin
.list_display()
.iter()
.map(|field| ColumnInfo {
field: field.to_string(),
label: humanize_field_name(field),
sortable: true,
})
.collect()
}
#[cfg(server)]
fn list_response_fields(model_admin: &dyn ModelAdmin) -> Vec<&str> {
model_admin.list_display()
}
#[server_fn]
pub async fn get_list(
model_name: String,
params: crate::adapters::ListQueryParams,
#[inject] site: Depends<AdminSiteKey, AdminSite>,
#[inject] db: Depends<AdminDatabaseKey, AdminDatabase>,
#[inject] AdminAuthenticatedUser(user): AdminAuthenticatedUser,
) -> Result<crate::adapters::ListResponse, ServerFnError> {
let model_admin = site.get_model_admin(&model_name).map_server_fn_error()?;
if !model_admin.has_view_permission(user.as_ref()).await {
return Err(ServerFnError::server(403, "Permission denied"));
}
let object_filters = require_object_filters(model_admin.as_ref(), user.as_ref())?;
let mut filter_condition: Option<FilterCondition> = None;
if let Some(search) = params.search.as_ref() {
let search_fields = model_admin.search_fields();
if !search_fields.is_empty() && !search.is_empty() {
let search_filters: Vec<FilterCondition> = search_fields
.iter()
.map(|field| {
FilterCondition::Single(Filter::new(
field.to_string(),
FilterOperator::Contains,
FilterValue::String(search.clone()),
))
})
.collect();
if !search_filters.is_empty() {
filter_condition = Some(FilterCondition::Or(search_filters));
}
}
}
let allowed_filter_fields = model_admin.list_filter();
let mut additional_filters = Vec::new();
for (field, value) in params.filters.iter() {
if !allowed_filter_fields.contains(&field.as_str()) {
return Err(ServerFnError::server(
400,
format!(
"Unknown filter field '{}'. Allowed filter fields: {:?}",
field, allowed_filter_fields
),
));
}
additional_filters.push(Filter::new(
field.clone(),
FilterOperator::Eq,
FilterValue::String(value.clone()),
));
}
additional_filters.extend(object_filters);
let sort_by = params
.sort_by
.as_deref()
.or_else(|| model_admin.ordering().first().copied());
if let Some(sort_field) = sort_by {
let raw_field = sort_field.strip_prefix('-').unwrap_or(sort_field);
let allowed_sort_fields = model_admin.list_display();
if !allowed_sort_fields.contains(&raw_field) {
return Err(ServerFnError::server(
400,
format!(
"Unknown sort field '{}'. Allowed sort fields: {:?}",
raw_field, allowed_sort_fields
),
));
}
}
let page = params.page.unwrap_or(1).max(1); let page_size = params
.page_size
.unwrap_or_else(|| {
let admin_settings = crate::settings::get_admin_settings();
model_admin
.list_per_page()
.unwrap_or(admin_settings.list_per_page) as u64
})
.min(MAX_PAGE_SIZE); let offset = (page - 1) * page_size;
let (mut results, count) = db
.list_with_condition_and_count::<AdminRecord>(
model_admin.table_name(),
filter_condition.as_ref(),
additional_filters,
sort_by,
offset,
page_size,
)
.await
.map_server_fn_error()?;
let object_ids = results
.iter()
.map(|record| {
record
.get(model_admin.pk_field())
.cloned()
.ok_or_else(|| ServerFnError::server(500, "List row is missing its primary key"))
})
.collect::<Result<Vec<_>, _>>()?;
let visible_fields = list_response_fields(model_admin.as_ref());
for record in &mut results {
retain_allowed_fields(record, &visible_fields);
}
let total_pages = if count > 0 {
count.div_ceil(page_size)
} else {
1
};
Ok(ListResponse {
model_name,
pk_field: model_admin.pk_field().to_string(),
count,
page,
page_size,
total_pages,
results,
object_ids,
available_filters: Some(build_filters(&model_admin)),
columns: Some(build_columns(&model_admin)),
})
}
#[cfg(all(test, server))]
mod tests {
use super::*;
use crate::core::ModelAdminConfig;
use serde_json::json;
use std::collections::HashMap;
#[test]
fn list_projection_excludes_unconfigured_primary_key_and_hidden_fields() {
let model_admin = ModelAdminConfig::new("User").with_list_display(vec!["name"]);
let mut record = HashMap::from([
("id".to_string(), json!(7)),
("name".to_string(), json!("Alice")),
("secret".to_string(), json!("hidden")),
]);
let visible_fields = list_response_fields(&model_admin);
retain_allowed_fields(&mut record, &visible_fields);
assert_eq!(
record,
HashMap::from([("name".to_string(), json!("Alice"))])
);
}
}