feat(search): implement search filtering in repository SQL queries
Wire SearchFilter into each repository's list method with entity-specific searchable columns: roasters (name, country, city), roasts (name, roaster, origin, producer), bags (roaster, roast), brews (roast, roaster), gear (make, model). Timeline passes None.
This commit is contained in:
parent
f616958e3c
commit
1e151123a0
6 changed files with 47 additions and 3 deletions
|
|
@ -160,7 +160,10 @@ impl BagRepository for SqlBagRepository {
|
||||||
&self,
|
&self,
|
||||||
filter: BagFilter,
|
filter: BagFilter,
|
||||||
request: &ListRequest<BagSortKey>,
|
request: &ListRequest<BagSortKey>,
|
||||||
|
search: Option<&str>,
|
||||||
) -> Result<Page<BagWithRoast>, RepositoryError> {
|
) -> Result<Page<BagWithRoast>, RepositoryError> {
|
||||||
|
use crate::infrastructure::repositories::pagination::SearchFilter;
|
||||||
|
|
||||||
let order_clause = Self::order_clause(request);
|
let order_clause = Self::order_clause(request);
|
||||||
|
|
||||||
// Build WHERE clause from filter
|
// Build WHERE clause from filter
|
||||||
|
|
@ -172,16 +175,21 @@ impl BagRepository for SqlBagRepository {
|
||||||
};
|
};
|
||||||
|
|
||||||
let count_query = match &where_clause {
|
let count_query = match &where_clause {
|
||||||
Some(w) => format!("SELECT COUNT(*) FROM bags b WHERE {w}"),
|
Some(w) => format!(
|
||||||
None => "SELECT COUNT(*) FROM bags".to_string(),
|
"SELECT COUNT(*) FROM bags b JOIN roasts r ON b.roast_id = r.id JOIN roasters rr ON r.roaster_id = rr.id WHERE {w}"
|
||||||
|
),
|
||||||
|
None => "SELECT COUNT(*) FROM bags b JOIN roasts r ON b.roast_id = r.id JOIN roasters rr ON r.roaster_id = rr.id".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let sf = search.and_then(|t| SearchFilter::new(t, vec!["rr.name", "r.name"]));
|
||||||
|
|
||||||
crate::infrastructure::repositories::pagination::paginate(
|
crate::infrastructure::repositories::pagination::paginate(
|
||||||
&self.pool,
|
&self.pool,
|
||||||
request,
|
request,
|
||||||
&base_query,
|
&base_query,
|
||||||
&count_query,
|
&count_query,
|
||||||
&order_clause,
|
&order_clause,
|
||||||
|
sf.as_ref(),
|
||||||
|record| Ok(Self::to_domain_with_roast(record)),
|
|record| Ok(Self::to_domain_with_roast(record)),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,10 @@ impl BrewRepository for SqlBrewRepository {
|
||||||
&self,
|
&self,
|
||||||
filter: BrewFilter,
|
filter: BrewFilter,
|
||||||
request: &ListRequest<BrewSortKey>,
|
request: &ListRequest<BrewSortKey>,
|
||||||
|
search: Option<&str>,
|
||||||
) -> Result<Page<BrewWithDetails>, RepositoryError> {
|
) -> Result<Page<BrewWithDetails>, RepositoryError> {
|
||||||
|
use crate::infrastructure::repositories::pagination::SearchFilter;
|
||||||
|
|
||||||
let order_clause = Self::order_clause(request);
|
let order_clause = Self::order_clause(request);
|
||||||
let where_clause = Self::build_where_clause(&filter);
|
let where_clause = Self::build_where_clause(&filter);
|
||||||
|
|
||||||
|
|
@ -209,6 +212,8 @@ impl BrewRepository for SqlBrewRepository {
|
||||||
let count_base = r"
|
let count_base = r"
|
||||||
SELECT COUNT(*) FROM brews br
|
SELECT COUNT(*) FROM brews br
|
||||||
JOIN bags b ON br.bag_id = b.id
|
JOIN bags b ON br.bag_id = b.id
|
||||||
|
JOIN roasts r ON b.roast_id = r.id
|
||||||
|
JOIN roasters rr ON r.roaster_id = rr.id
|
||||||
";
|
";
|
||||||
|
|
||||||
let count_query = match &where_clause {
|
let count_query = match &where_clause {
|
||||||
|
|
@ -216,12 +221,15 @@ impl BrewRepository for SqlBrewRepository {
|
||||||
None => count_base.to_string(),
|
None => count_base.to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let sf = search.and_then(|t| SearchFilter::new(t, vec!["r.name", "rr.name"]));
|
||||||
|
|
||||||
crate::infrastructure::repositories::pagination::paginate(
|
crate::infrastructure::repositories::pagination::paginate(
|
||||||
&self.pool,
|
&self.pool,
|
||||||
request,
|
request,
|
||||||
&base_query,
|
&base_query,
|
||||||
&count_query,
|
&count_query,
|
||||||
&order_clause,
|
&order_clause,
|
||||||
|
sf.as_ref(),
|
||||||
|record| Ok(Self::to_domain_with_details(record)),
|
|record| Ok(Self::to_domain_with_details(record)),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,10 @@ impl GearRepository for SqlGearRepository {
|
||||||
&self,
|
&self,
|
||||||
filter: GearFilter,
|
filter: GearFilter,
|
||||||
request: &ListRequest<GearSortKey>,
|
request: &ListRequest<GearSortKey>,
|
||||||
|
search: Option<&str>,
|
||||||
) -> Result<Page<Gear>, RepositoryError> {
|
) -> Result<Page<Gear>, RepositoryError> {
|
||||||
|
use crate::infrastructure::repositories::pagination::SearchFilter;
|
||||||
|
|
||||||
let order_clause = Self::order_clause(request);
|
let order_clause = Self::order_clause(request);
|
||||||
let where_clause = Self::build_where_clause(&filter);
|
let where_clause = Self::build_where_clause(&filter);
|
||||||
|
|
||||||
|
|
@ -118,12 +121,15 @@ impl GearRepository for SqlGearRepository {
|
||||||
None => "SELECT COUNT(*) FROM gear".to_string(),
|
None => "SELECT COUNT(*) FROM gear".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let sf = search.and_then(|t| SearchFilter::new(t, vec!["make", "model"]));
|
||||||
|
|
||||||
crate::infrastructure::repositories::pagination::paginate(
|
crate::infrastructure::repositories::pagination::paginate(
|
||||||
&self.pool,
|
&self.pool,
|
||||||
request,
|
request,
|
||||||
&base_query,
|
&base_query,
|
||||||
&count_query,
|
&count_query,
|
||||||
&order_clause,
|
&order_clause,
|
||||||
|
sf.as_ref(),
|
||||||
Self::to_domain,
|
Self::to_domain,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
||||||
|
|
@ -190,11 +190,16 @@ impl RoasterRepository for SqlRoasterRepository {
|
||||||
async fn list(
|
async fn list(
|
||||||
&self,
|
&self,
|
||||||
request: &ListRequest<RoasterSortKey>,
|
request: &ListRequest<RoasterSortKey>,
|
||||||
|
search: Option<&str>,
|
||||||
) -> Result<Page<Roaster>, RepositoryError> {
|
) -> Result<Page<Roaster>, RepositoryError> {
|
||||||
|
use crate::infrastructure::repositories::pagination::SearchFilter;
|
||||||
|
|
||||||
let order_clause = Self::order_clause(request);
|
let order_clause = Self::order_clause(request);
|
||||||
let base_query =
|
let base_query =
|
||||||
"SELECT id, name, slug, country, city, homepage, notes, created_at FROM roasters";
|
"SELECT id, name, slug, country, city, homepage, notes, created_at FROM roasters";
|
||||||
let count_query = "SELECT COUNT(*) FROM roasters";
|
let count_query = "SELECT COUNT(*) FROM roasters";
|
||||||
|
let sf =
|
||||||
|
search.and_then(|t| SearchFilter::new(t, vec!["name", "country", "COALESCE(city,'')"]));
|
||||||
|
|
||||||
crate::infrastructure::repositories::pagination::paginate(
|
crate::infrastructure::repositories::pagination::paginate(
|
||||||
&self.pool,
|
&self.pool,
|
||||||
|
|
@ -202,6 +207,7 @@ impl RoasterRepository for SqlRoasterRepository {
|
||||||
base_query,
|
base_query,
|
||||||
count_query,
|
count_query,
|
||||||
&order_clause,
|
&order_clause,
|
||||||
|
sf.as_ref(),
|
||||||
|record| Ok(Self::into_domain(record)),
|
|record| Ok(Self::into_domain(record)),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
||||||
|
|
@ -249,10 +249,24 @@ impl RoastRepository for SqlRoastRepository {
|
||||||
async fn list(
|
async fn list(
|
||||||
&self,
|
&self,
|
||||||
request: &ListRequest<RoastSortKey>,
|
request: &ListRequest<RoastSortKey>,
|
||||||
|
search: Option<&str>,
|
||||||
) -> Result<Page<RoastWithRoaster>, RepositoryError> {
|
) -> Result<Page<RoastWithRoaster>, RepositoryError> {
|
||||||
|
use crate::infrastructure::repositories::pagination::SearchFilter;
|
||||||
|
|
||||||
let order_clause = Self::order_clause(request);
|
let order_clause = Self::order_clause(request);
|
||||||
let base_query = "SELECT r.id, r.roaster_id, r.name, r.slug, r.origin, r.region, r.producer, r.process, r.tasting_notes, r.created_at, ro.name AS roaster_name, ro.slug AS roaster_slug \n FROM roasts r \n JOIN roasters ro ON ro.id = r.roaster_id";
|
let base_query = "SELECT r.id, r.roaster_id, r.name, r.slug, r.origin, r.region, r.producer, r.process, r.tasting_notes, r.created_at, ro.name AS roaster_name, ro.slug AS roaster_slug \n FROM roasts r \n JOIN roasters ro ON ro.id = r.roaster_id";
|
||||||
let count_query = "SELECT COUNT(*) FROM roasts";
|
let count_query = "SELECT COUNT(*) FROM roasts r JOIN roasters ro ON ro.id = r.roaster_id";
|
||||||
|
let sf = search.and_then(|t| {
|
||||||
|
SearchFilter::new(
|
||||||
|
t,
|
||||||
|
vec![
|
||||||
|
"r.name",
|
||||||
|
"ro.name",
|
||||||
|
"COALESCE(r.origin,'')",
|
||||||
|
"COALESCE(r.producer,'')",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
crate::infrastructure::repositories::pagination::paginate(
|
crate::infrastructure::repositories::pagination::paginate(
|
||||||
&self.pool,
|
&self.pool,
|
||||||
|
|
@ -260,6 +274,7 @@ impl RoastRepository for SqlRoastRepository {
|
||||||
base_query,
|
base_query,
|
||||||
count_query,
|
count_query,
|
||||||
&order_clause,
|
&order_clause,
|
||||||
|
sf.as_ref(),
|
||||||
|record: RoastWithRoasterRecord| record.into_with_roaster(),
|
|record: RoastWithRoasterRecord| record.into_with_roaster(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,7 @@ impl TimelineEventRepository for SqlTimelineEventRepository {
|
||||||
base_query,
|
base_query,
|
||||||
count_query,
|
count_query,
|
||||||
&order_clause,
|
&order_clause,
|
||||||
|
None,
|
||||||
|record: TimelineEventRecord| record.into_domain(),
|
|record: TimelineEventRecord| record.into_domain(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue