refactor(roasts): extract helpers to remove too_many_lines suppression
- Add empty_to_none() to replace four repeated empty-string-to-None blocks - Extract insert_timeline_event() from insert() to isolate timeline logic - Remove #[allow(clippy::too_many_lines)] from RoastRepository impl
This commit is contained in:
parent
09a629db9a
commit
f1e8984ffa
1 changed files with 77 additions and 82 deletions
|
|
@ -10,7 +10,7 @@ use crate::domain::listing::{ListRequest, Page, SortDirection};
|
||||||
use crate::domain::repositories::RoastRepository;
|
use crate::domain::repositories::RoastRepository;
|
||||||
use crate::domain::roasts::{NewRoast, Roast, RoastSortKey, RoastWithRoaster, UpdateRoast};
|
use crate::domain::roasts::{NewRoast, Roast, RoastSortKey, RoastWithRoaster, UpdateRoast};
|
||||||
use crate::domain::timeline::TimelineEventDetail;
|
use crate::domain::timeline::TimelineEventDetail;
|
||||||
use crate::infrastructure::database::DatabasePool;
|
use crate::infrastructure::database::{DatabasePool, DatabaseTransaction};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SqlRoastRepository {
|
pub struct SqlRoastRepository {
|
||||||
|
|
@ -50,86 +50,15 @@ impl SqlRoastRepository {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)] // Repository impl has many methods
|
async fn insert_timeline_event(
|
||||||
#[async_trait]
|
tx: &mut DatabaseTransaction<'_>,
|
||||||
impl RoastRepository for SqlRoastRepository {
|
roast: &Roast,
|
||||||
async fn insert(&self, new_roast: NewRoast) -> Result<Roast, RepositoryError> {
|
) -> Result<(), RepositoryError> {
|
||||||
let mut tx = self
|
|
||||||
.pool
|
|
||||||
.begin()
|
|
||||||
.await
|
|
||||||
.map_err(|err| RepositoryError::unexpected(err.to_string()))?;
|
|
||||||
|
|
||||||
let slug = new_roast.slug();
|
|
||||||
let NewRoast {
|
|
||||||
roaster_id,
|
|
||||||
name,
|
|
||||||
origin,
|
|
||||||
region,
|
|
||||||
producer,
|
|
||||||
tasting_notes,
|
|
||||||
process,
|
|
||||||
} = new_roast;
|
|
||||||
|
|
||||||
let origin_value = if origin.trim().is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(origin)
|
|
||||||
};
|
|
||||||
let region_value = if region.trim().is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(region)
|
|
||||||
};
|
|
||||||
let producer_value = if producer.trim().is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(producer)
|
|
||||||
};
|
|
||||||
let process_value = if process.trim().is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(process)
|
|
||||||
};
|
|
||||||
|
|
||||||
let created_at = Utc::now();
|
|
||||||
let notes_json = Self::encode_notes(&tasting_notes)?;
|
|
||||||
|
|
||||||
let record = query_as::<_, RoastRecord>(
|
|
||||||
"INSERT INTO roasts (roaster_id, name, slug, origin, region, producer, process, tasting_notes, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\
|
|
||||||
RETURNING id, roaster_id, name, slug, origin, region, producer, process, tasting_notes, created_at",
|
|
||||||
)
|
|
||||||
.bind(i64::from(roaster_id))
|
|
||||||
.bind(&name)
|
|
||||||
.bind(&slug)
|
|
||||||
.bind(origin_value.as_deref())
|
|
||||||
.bind(region_value.as_deref())
|
|
||||||
.bind(producer_value.as_deref())
|
|
||||||
.bind(process_value.as_deref())
|
|
||||||
.bind(notes_json.as_deref())
|
|
||||||
.bind(created_at)
|
|
||||||
.fetch_one(&mut *tx)
|
|
||||||
.await
|
|
||||||
.map_err(|err| {
|
|
||||||
if let sqlx::Error::Database(db_err) = &err
|
|
||||||
&& db_err.is_unique_violation()
|
|
||||||
{
|
|
||||||
return RepositoryError::conflict(
|
|
||||||
"A roast with this name already exists for this roaster",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
map_insert_error(err, "unknown roaster reference")
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let roast = record.into_roast()?;
|
|
||||||
|
|
||||||
// Fetch roaster info for timeline event
|
|
||||||
let roaster_info: Option<(String, String)> =
|
let roaster_info: Option<(String, String)> =
|
||||||
query_as("SELECT name, slug FROM roasters WHERE id = ?")
|
query_as("SELECT name, slug FROM roasters WHERE id = ?")
|
||||||
.bind(i64::from(roast.roaster_id))
|
.bind(i64::from(roast.roaster_id))
|
||||||
.fetch_optional(&mut *tx)
|
.fetch_optional(&mut **tx)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| RepositoryError::unexpected(err.to_string()))?;
|
.map_err(|err| RepositoryError::unexpected(err.to_string()))?;
|
||||||
|
|
||||||
|
|
@ -141,7 +70,7 @@ impl RoastRepository for SqlRoastRepository {
|
||||||
let details = vec![
|
let details = vec![
|
||||||
TimelineEventDetail {
|
TimelineEventDetail {
|
||||||
label: "Roaster".to_string(),
|
label: "Roaster".to_string(),
|
||||||
value: roaster_label.clone(),
|
value: roaster_label,
|
||||||
},
|
},
|
||||||
TimelineEventDetail {
|
TimelineEventDetail {
|
||||||
label: "Origin".to_string(),
|
label: "Origin".to_string(),
|
||||||
|
|
@ -185,13 +114,79 @@ impl RoastRepository for SqlRoastRepository {
|
||||||
.bind(&roast.name)
|
.bind(&roast.name)
|
||||||
.bind(details_json)
|
.bind(details_json)
|
||||||
.bind(tasting_notes_json.as_deref())
|
.bind(tasting_notes_json.as_deref())
|
||||||
.bind(&roast.slug) // slug = roast's own slug
|
.bind(&roast.slug)
|
||||||
.bind(roaster_slug.as_deref()) // roaster_slug from the roaster
|
.bind(roaster_slug.as_deref())
|
||||||
.bind::<Option<&str>>(None) // brew_data_json not applicable
|
.bind::<Option<&str>>(None)
|
||||||
.execute(&mut *tx)
|
.execute(&mut **tx)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| RepositoryError::unexpected(err.to_string()))?;
|
.map_err(|err| RepositoryError::unexpected(err.to_string()))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn empty_to_none(s: String) -> Option<String> {
|
||||||
|
if s.trim().is_empty() { None } else { Some(s) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl RoastRepository for SqlRoastRepository {
|
||||||
|
async fn insert(&self, new_roast: NewRoast) -> Result<Roast, RepositoryError> {
|
||||||
|
let mut tx = self
|
||||||
|
.pool
|
||||||
|
.begin()
|
||||||
|
.await
|
||||||
|
.map_err(|err| RepositoryError::unexpected(err.to_string()))?;
|
||||||
|
|
||||||
|
let slug = new_roast.slug();
|
||||||
|
let NewRoast {
|
||||||
|
roaster_id,
|
||||||
|
name,
|
||||||
|
origin,
|
||||||
|
region,
|
||||||
|
producer,
|
||||||
|
tasting_notes,
|
||||||
|
process,
|
||||||
|
} = new_roast;
|
||||||
|
|
||||||
|
let origin_value = empty_to_none(origin);
|
||||||
|
let region_value = empty_to_none(region);
|
||||||
|
let producer_value = empty_to_none(producer);
|
||||||
|
let process_value = empty_to_none(process);
|
||||||
|
|
||||||
|
let created_at = Utc::now();
|
||||||
|
let notes_json = Self::encode_notes(&tasting_notes)?;
|
||||||
|
|
||||||
|
let record = query_as::<_, RoastRecord>(
|
||||||
|
"INSERT INTO roasts (roaster_id, name, slug, origin, region, producer, process, tasting_notes, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\
|
||||||
|
RETURNING id, roaster_id, name, slug, origin, region, producer, process, tasting_notes, created_at",
|
||||||
|
)
|
||||||
|
.bind(i64::from(roaster_id))
|
||||||
|
.bind(&name)
|
||||||
|
.bind(&slug)
|
||||||
|
.bind(origin_value.as_deref())
|
||||||
|
.bind(region_value.as_deref())
|
||||||
|
.bind(producer_value.as_deref())
|
||||||
|
.bind(process_value.as_deref())
|
||||||
|
.bind(notes_json.as_deref())
|
||||||
|
.bind(created_at)
|
||||||
|
.fetch_one(&mut *tx)
|
||||||
|
.await
|
||||||
|
.map_err(|err| {
|
||||||
|
if let sqlx::Error::Database(db_err) = &err
|
||||||
|
&& db_err.is_unique_violation()
|
||||||
|
{
|
||||||
|
return RepositoryError::conflict(
|
||||||
|
"A roast with this name already exists for this roaster",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
map_insert_error(err, "unknown roaster reference")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let roast = record.into_roast()?;
|
||||||
|
|
||||||
|
Self::insert_timeline_event(&mut tx, &roast).await?;
|
||||||
|
|
||||||
tx.commit()
|
tx.commit()
|
||||||
.await
|
.await
|
||||||
.map_err(|err| RepositoryError::unexpected(err.to_string()))?;
|
.map_err(|err| RepositoryError::unexpected(err.to_string()))?;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue