use libertas_core::{ error::{CoreError, CoreResult}, schema::{ListMediaOptions, SortOrder}, }; use sqlx::QueryBuilder as SqlxQueryBuilder; pub trait QueryBuilder { fn apply_options_to_query<'a>( &self, query: SqlxQueryBuilder<'a, sqlx::Postgres>, options: &'a T, ) -> CoreResult>; } pub struct MediaQueryBuilder { allowed_sort_columns: Vec, } impl MediaQueryBuilder { pub fn new(allowed_sort_columns: Vec) -> Self { Self { allowed_sort_columns, } } fn validate_sort_column<'a>(&self, column: &'a str) -> CoreResult<&'a str> { if self.allowed_sort_columns.contains(&column.to_string()) { Ok(column) } else { Err(CoreError::Validation(format!( "Sorting by '{}' is not supported", column ))) } } pub fn apply_filters_to_query<'a>( &self, mut query: SqlxQueryBuilder<'a, sqlx::Postgres>, options: &'a ListMediaOptions, ) -> CoreResult<(SqlxQueryBuilder<'a, sqlx::Postgres>, i64)> { let mut metadata_filter_count = 0; if let Some(filter) = &options.filter { if let Some(mime) = &filter.mime_type { query.push(" AND media.mime_type = "); query.push_bind(mime); } if let Some(metadata_filters) = &filter.metadata_filters { if !metadata_filters.is_empty() { metadata_filter_count = metadata_filters.len() as i64; query.push(" JOIN media_metadata mm ON media.id = mm.media_id "); query.push(" AND ( "); for (i, filter) in metadata_filters.iter().enumerate() { if i > 0 { query.push(" OR "); } query.push(" ( mm.tag_name = "); query.push_bind(&filter.tag_name); query.push(" AND mm.tag_value = "); query.push_bind(&filter.tag_value); query.push(" ) "); } query.push(" ) "); } } } Ok((query, metadata_filter_count)) } pub fn apply_sorting_to_query<'a>( &self, mut query: SqlxQueryBuilder<'a, sqlx::Postgres>, options: &'a ListMediaOptions, ) -> CoreResult> { if let Some(sort) = &options.sort { let column = self.validate_sort_column(&sort.sort_by)?; let direction = match sort.sort_order { SortOrder::Asc => "ASC", SortOrder::Desc => "DESC", }; let nulls_order = if direction == "ASC" { "NULLS LAST" } else { "NULLS FIRST" }; let order_by_clause = format!("ORDER BY {} {} {}", column, direction, nulls_order); query.push(order_by_clause); } else { query.push(" ORDER BY media.created_at DESC NULLS LAST "); } Ok(query) } pub fn apply_pagination_to_query<'a>( &self, mut query: SqlxQueryBuilder<'a, sqlx::Postgres>, options: &'a ListMediaOptions, ) -> CoreResult> { if let Some(pagination) = &options.pagination { let limit = pagination.limit as i64; let offset = (pagination.page.saturating_sub(1) as i64) * limit; query.push(" LIMIT "); query.push_bind(limit); query.push(" OFFSET "); query.push_bind(offset); } Ok(query) } }