use axum::{ extract::{FromRequestParts, Query}, http::request::Parts, }; use libertas_core::{ error::CoreError, schema::{ FilterCondition, FilterOperator, FilterParams, ListMediaOptions, MetadataFilter, PaginationParams, SortOrder, SortParams, }, }; use crate::{error::ApiError, schema::ListMediaParams, state::AppState}; pub struct ApiListMediaOptions(pub ListMediaOptions); const DEFAULT_PAGE: u32 = 1; const DEFAULT_LIMIT: u32 = 50; impl From for ListMediaOptions { fn from(params: ListMediaParams) -> Self { let sort = params.sort_by.map(|field| { let order = match params.order.as_deref() { Some("asc") => SortOrder::Asc, _ => SortOrder::Desc, }; SortParams { sort_by: field, sort_order: order, } }); let metadata_filters = if params.metadata.is_empty() { None } else { Some( params .metadata .into_iter() .filter_map(|s| { s.split_once(":").map(|(key, value)| MetadataFilter { tag_name: key.to_string(), tag_value: value.to_string(), }) }) .collect::>(), ) }; let pagination = { let page = params.page.unwrap_or(DEFAULT_PAGE); let limit = params.limit.unwrap_or(DEFAULT_LIMIT); let page = if page == 0 { DEFAULT_PAGE } else { page }; let limit = if limit > (DEFAULT_LIMIT * 2) { DEFAULT_LIMIT * 2 } else { limit }; Some(PaginationParams { page, limit }) }; let conditions = if params.filters.is_empty() { None } else { let mut conds = Vec::new(); for filter_str in params.filters { let parts: Vec<&str> = filter_str.splitn(3, ':').collect(); if parts.len() == 3 { let field = parts[0].to_string(); let op_str = parts[1]; let value = parts[2].to_string(); let operator = match op_str.to_lowercase().as_str() { "eq" => Some(FilterOperator::Eq), "neq" => Some(FilterOperator::Neq), "like" => Some(FilterOperator::Like), "gt" => Some(FilterOperator::Gt), "lt" => Some(FilterOperator::Lt), "gte" => Some(FilterOperator::Gte), "lte" => Some(FilterOperator::Lte), _ => None, }; if let Some(op) = operator { conds.push(FilterCondition { field, operator: op, value, }); } } } if conds.is_empty() { None } else { Some(conds) } }; let filter = Some(FilterParams { mime_type: params.mime_type, metadata_filters, conditions, }); ListMediaOptions { sort, filter, pagination, } } } impl FromRequestParts for ApiListMediaOptions { type Rejection = ApiError; async fn from_request_parts( parts: &mut Parts, state: &AppState, ) -> Result { let Query(raw_params) = Query::>::from_request_parts(parts, state) .await .map_err(|e| ApiError::from(CoreError::Validation(e.to_string())))?; let mut params = ListMediaParams { sort_by: None, order: None, mime_type: None, metadata: Vec::new(), filters: Vec::new(), page: None, limit: None, }; for (key, value) in raw_params { match key.as_str() { "sort_by" => params.sort_by = Some(value), "order" => params.order = Some(value), "mime_type" => params.mime_type = Some(value), "metadata" => params.metadata.push(value), "filters" => params.filters.push(value), "page" => params.page = value.parse().ok(), "limit" => params.limit = value.parse().ok(), _ => {} } } Ok(ApiListMediaOptions(params.into())) } }