diff --git a/src/infrastructure/repositories/analytics/ai_usage.rs b/src/infrastructure/repositories/analytics/ai_usage.rs index 0c17fef..85990eb 100644 --- a/src/infrastructure/repositories/analytics/ai_usage.rs +++ b/src/infrastructure/repositories/analytics/ai_usage.rs @@ -17,20 +17,6 @@ impl SqlAiUsageRepository { pub fn new(pool: DatabasePool) -> Self { Self { pool } } - - fn to_domain(record: AiUsageRecord) -> AiUsage { - AiUsage { - id: AiUsageId::from(record.id), - user_id: UserId::from(record.user_id), - model: record.model, - endpoint: record.endpoint, - prompt_tokens: record.prompt_tokens, - completion_tokens: record.completion_tokens, - total_tokens: record.total_tokens, - cost: record.cost, - created_at: record.created_at, - } - } } #[async_trait] @@ -54,7 +40,7 @@ impl AiUsageRepository for SqlAiUsageRepository { .await .map_err(|err| RepositoryError::unexpected(err.to_string()))?; - Ok(Self::to_domain(record)) + Ok(record.into()) } async fn summary_for_user(&self, user_id: UserId) -> Result { @@ -98,6 +84,22 @@ struct AiUsageRecord { created_at: DateTime, } +impl From for AiUsage { + fn from(record: AiUsageRecord) -> Self { + AiUsage { + id: AiUsageId::from(record.id), + user_id: UserId::from(record.user_id), + model: record.model, + endpoint: record.endpoint, + prompt_tokens: record.prompt_tokens, + completion_tokens: record.completion_tokens, + total_tokens: record.total_tokens, + cost: record.cost, + created_at: record.created_at, + } + } +} + #[derive(sqlx::FromRow)] #[allow(clippy::struct_field_names)] struct AiUsageSummaryRecord { diff --git a/src/infrastructure/repositories/auth/passkey_credentials.rs b/src/infrastructure/repositories/auth/passkey_credentials.rs index a7e6870..8014a00 100644 --- a/src/infrastructure/repositories/auth/passkey_credentials.rs +++ b/src/infrastructure/repositories/auth/passkey_credentials.rs @@ -17,26 +17,6 @@ impl SqlPasskeyCredentialRepository { pub fn new(pool: DatabasePool) -> Self { Self { pool } } - - fn to_domain(record: PasskeyCredentialRecord) -> PasskeyCredential { - let PasskeyCredentialRecord { - id, - user_id, - credential_json, - name, - created_at, - last_used_at, - } = record; - - PasskeyCredential { - id: PasskeyCredentialId::from(id), - user_id: UserId::from(user_id), - credential_json, - name, - created_at, - last_used_at, - } - } } #[async_trait] @@ -61,7 +41,7 @@ impl PasskeyCredentialRepository for SqlPasskeyCredentialRepository { RepositoryError::unexpected(format!("failed to insert passkey credential: {err}")) })?; - Ok(Self::to_domain(record)) + Ok(record.into()) } async fn get(&self, id: PasskeyCredentialId) -> Result { @@ -82,7 +62,7 @@ impl PasskeyCredentialRepository for SqlPasskeyCredentialRepository { } })?; - Ok(Self::to_domain(record)) + Ok(record.into()) } async fn list_by_user( @@ -104,7 +84,7 @@ impl PasskeyCredentialRepository for SqlPasskeyCredentialRepository { RepositoryError::unexpected(format!("failed to list passkey credentials: {err}")) })?; - Ok(records.into_iter().map(Self::to_domain).collect()) + Ok(records.into_iter().map(Into::into).collect()) } async fn list_all(&self) -> Result, RepositoryError> { @@ -123,7 +103,7 @@ impl PasskeyCredentialRepository for SqlPasskeyCredentialRepository { )) })?; - Ok(records.into_iter().map(Self::to_domain).collect()) + Ok(records.into_iter().map(Into::into).collect()) } async fn update_credential_json( @@ -183,3 +163,16 @@ struct PasskeyCredentialRecord { created_at: DateTime, last_used_at: Option>, } + +impl From for PasskeyCredential { + fn from(record: PasskeyCredentialRecord) -> Self { + PasskeyCredential { + id: PasskeyCredentialId::from(record.id), + user_id: UserId::from(record.user_id), + credential_json: record.credential_json, + name: record.name, + created_at: record.created_at, + last_used_at: record.last_used_at, + } + } +} diff --git a/src/infrastructure/repositories/auth/registration_tokens.rs b/src/infrastructure/repositories/auth/registration_tokens.rs index 5cca96e..ae88c04 100644 --- a/src/infrastructure/repositories/auth/registration_tokens.rs +++ b/src/infrastructure/repositories/auth/registration_tokens.rs @@ -17,26 +17,6 @@ impl SqlRegistrationTokenRepository { pub fn new(pool: DatabasePool) -> Self { Self { pool } } - - fn to_domain(record: RegistrationTokenRecord) -> RegistrationToken { - let RegistrationTokenRecord { - id, - token_hash, - created_at, - expires_at, - used_at, - used_by_user_id, - } = record; - - RegistrationToken { - id: RegistrationTokenId::from(id), - token_hash, - created_at, - expires_at, - used_at, - used_by_user_id: used_by_user_id.map(UserId::from), - } - } } #[async_trait] @@ -61,7 +41,7 @@ impl RegistrationTokenRepository for SqlRegistrationTokenRepository { RepositoryError::unexpected(format!("failed to insert registration token: {err}")) })?; - Ok(Self::to_domain(record)) + Ok(record.into()) } async fn get_by_token_hash( @@ -83,7 +63,7 @@ impl RegistrationTokenRepository for SqlRegistrationTokenRepository { })? .ok_or(RepositoryError::NotFound)?; - Ok(Self::to_domain(record)) + Ok(record.into()) } async fn mark_used( @@ -119,3 +99,16 @@ struct RegistrationTokenRecord { used_at: Option>, used_by_user_id: Option, } + +impl From for RegistrationToken { + fn from(record: RegistrationTokenRecord) -> Self { + RegistrationToken { + id: RegistrationTokenId::from(record.id), + token_hash: record.token_hash, + created_at: record.created_at, + expires_at: record.expires_at, + used_at: record.used_at, + used_by_user_id: record.used_by_user_id.map(UserId::from), + } + } +} diff --git a/src/infrastructure/repositories/auth/sessions.rs b/src/infrastructure/repositories/auth/sessions.rs index b820d58..fb3610e 100644 --- a/src/infrastructure/repositories/auth/sessions.rs +++ b/src/infrastructure/repositories/auth/sessions.rs @@ -16,24 +16,6 @@ impl SqlSessionRepository { pub fn new(pool: DatabasePool) -> Self { Self { pool } } - - fn to_domain(record: SessionRecord) -> Session { - let SessionRecord { - id, - user_id, - session_token_hash, - created_at, - expires_at, - } = record; - - Session::new( - SessionId::from(id), - UserId::from(user_id), - session_token_hash, - created_at, - expires_at, - ) - } } #[async_trait] @@ -59,7 +41,7 @@ impl SessionRepository for SqlSessionRepository { RepositoryError::unexpected(format!("failed to insert session: {err}")) })?; - Ok(Self::to_domain(record)) + Ok(record.into()) } async fn get(&self, id: SessionId) -> Result { @@ -72,7 +54,7 @@ impl SessionRepository for SqlSessionRepository { .map_err(|err| RepositoryError::unexpected(format!("failed to get session: {err}")))? .ok_or(RepositoryError::NotFound)?; - Ok(Self::to_domain(record)) + Ok(record.into()) } async fn get_by_token_hash(&self, token_hash: &str) -> Result { @@ -87,7 +69,7 @@ impl SessionRepository for SqlSessionRepository { })? .ok_or(RepositoryError::NotFound)?; - Ok(Self::to_domain(record)) + Ok(record.into()) } async fn delete(&self, id: SessionId) -> Result<(), RepositoryError> { @@ -124,3 +106,15 @@ struct SessionRecord { created_at: chrono::DateTime, expires_at: chrono::DateTime, } + +impl From for Session { + fn from(record: SessionRecord) -> Self { + Session::new( + SessionId::from(record.id), + UserId::from(record.user_id), + record.session_token_hash, + record.created_at, + record.expires_at, + ) + } +} diff --git a/src/infrastructure/repositories/auth/tokens.rs b/src/infrastructure/repositories/auth/tokens.rs index a9a8e11..1484b73 100644 --- a/src/infrastructure/repositories/auth/tokens.rs +++ b/src/infrastructure/repositories/auth/tokens.rs @@ -17,28 +17,6 @@ impl SqlTokenRepository { pub fn new(pool: DatabasePool) -> Self { Self { pool } } - - fn to_domain(record: TokenRecord) -> Token { - let TokenRecord { - id, - user_id, - token_hash, - name, - created_at, - last_used_at, - revoked_at, - } = record; - - Token::new( - TokenId::from(id), - UserId::from(user_id), - token_hash, - name, - created_at, - last_used_at, - revoked_at, - ) - } } #[async_trait] @@ -67,7 +45,7 @@ impl TokenRepository for SqlTokenRepository { RepositoryError::unexpected(err.to_string()) })?; - Ok(Self::to_domain(record)) + Ok(record.into()) } async fn get(&self, id: TokenId) -> Result { @@ -80,7 +58,7 @@ impl TokenRepository for SqlTokenRepository { .map_err(|err| RepositoryError::unexpected(err.to_string()))? .ok_or(RepositoryError::NotFound)?; - Ok(Self::to_domain(record)) + Ok(record.into()) } async fn get_by_token_hash(&self, token_hash: &str) -> Result { @@ -93,7 +71,7 @@ impl TokenRepository for SqlTokenRepository { .map_err(|err| RepositoryError::unexpected(err.to_string()))? .ok_or(RepositoryError::NotFound)?; - Ok(Self::to_domain(record)) + Ok(record.into()) } async fn list_by_user(&self, user_id: UserId) -> Result, RepositoryError> { @@ -105,7 +83,7 @@ impl TokenRepository for SqlTokenRepository { .await .map_err(|err| RepositoryError::unexpected(err.to_string()))?; - Ok(records.into_iter().map(Self::to_domain).collect()) + Ok(records.into_iter().map(Into::into).collect()) } async fn revoke(&self, id: TokenId) -> Result { @@ -120,7 +98,7 @@ impl TokenRepository for SqlTokenRepository { .map_err(|err| RepositoryError::unexpected(err.to_string()))?; match record { - Some(record) => Ok(Self::to_domain(record)), + Some(record) => Ok(record.into()), None => Err(RepositoryError::NotFound), } } @@ -149,3 +127,17 @@ struct TokenRecord { last_used_at: Option>, revoked_at: Option>, } + +impl From for Token { + fn from(record: TokenRecord) -> Self { + Token::new( + TokenId::from(record.id), + UserId::from(record.user_id), + record.token_hash, + record.name, + record.created_at, + record.last_used_at, + record.revoked_at, + ) + } +} diff --git a/src/infrastructure/repositories/auth/users.rs b/src/infrastructure/repositories/auth/users.rs index 3507c05..faed13f 100644 --- a/src/infrastructure/repositories/auth/users.rs +++ b/src/infrastructure/repositories/auth/users.rs @@ -17,17 +17,6 @@ impl SqlUserRepository { pub fn new(pool: DatabasePool) -> Self { Self { pool } } - - fn to_domain(record: UserRecord) -> User { - let UserRecord { - id, - username, - uuid, - created_at, - } = record; - - User::new(UserId::from(id), username, uuid, created_at) - } } #[async_trait] @@ -49,7 +38,7 @@ impl UserRepository for SqlUserRepository { RepositoryError::unexpected(err.to_string()) })?; - Ok(Self::to_domain(record)) + Ok(record.into()) } async fn get(&self, id: UserId) -> Result { @@ -62,7 +51,7 @@ impl UserRepository for SqlUserRepository { .map_err(|err| RepositoryError::unexpected(err.to_string()))? .ok_or(RepositoryError::NotFound)?; - Ok(Self::to_domain(record)) + Ok(record.into()) } async fn get_by_username(&self, username: &str) -> Result { @@ -75,7 +64,7 @@ impl UserRepository for SqlUserRepository { .map_err(|err| RepositoryError::unexpected(err.to_string()))? .ok_or(RepositoryError::NotFound)?; - Ok(Self::to_domain(record)) + Ok(record.into()) } async fn get_by_uuid(&self, uuid: &str) -> Result { @@ -88,7 +77,7 @@ impl UserRepository for SqlUserRepository { .map_err(|err| RepositoryError::unexpected(err.to_string()))? .ok_or(RepositoryError::NotFound)?; - Ok(Self::to_domain(record)) + Ok(record.into()) } async fn exists(&self) -> Result { @@ -110,7 +99,7 @@ impl UserRepository for SqlUserRepository { .await .map_err(|err| RepositoryError::unexpected(err.to_string()))?; - Ok(records.into_iter().map(Self::to_domain).collect()) + Ok(records.into_iter().map(Into::into).collect()) } } @@ -121,3 +110,14 @@ struct UserRecord { uuid: String, created_at: DateTime, } + +impl From for User { + fn from(record: UserRecord) -> Self { + User::new( + UserId::from(record.id), + record.username, + record.uuid, + record.created_at, + ) + } +} diff --git a/src/infrastructure/repositories/coffee/bags.rs b/src/infrastructure/repositories/coffee/bags.rs index 56104af..ad22c1a 100644 --- a/src/infrastructure/repositories/coffee/bags.rs +++ b/src/infrastructure/repositories/coffee/bags.rs @@ -11,7 +11,7 @@ use crate::infrastructure::database::DatabasePool; use crate::infrastructure::repositories::macros::push_update_field; const BASE_SELECT: &str = r" - SELECT + SELECT b.id, b.roast_id, b.roast_date, b.amount, b.remaining, b.closed, b.finished_at, b.created_at, b.updated_at, r.name as roast_name, r.slug as roast_slug, rr.name as roaster_name, rr.slug as roaster_slug @@ -49,40 +49,6 @@ impl SqlBagRepository { } } - fn to_domain(record: BagRecord) -> Bag { - Bag { - id: BagId::new(record.id), - roast_id: RoastId::new(record.roast_id), - roast_date: record.roast_date, - amount: record.amount, - remaining: record.remaining, - closed: record.closed, - finished_at: record.finished_at, - created_at: record.created_at, - updated_at: record.updated_at, - } - } - - fn to_domain_with_roast(record: BagWithRoastRecord) -> BagWithRoast { - BagWithRoast { - bag: Bag { - id: BagId::new(record.id), - roast_id: RoastId::new(record.roast_id), - roast_date: record.roast_date, - amount: record.amount, - remaining: record.remaining, - closed: record.closed, - finished_at: record.finished_at, - created_at: record.created_at, - updated_at: record.updated_at, - }, - roast_name: record.roast_name, - roaster_name: record.roaster_name, - roast_slug: record.roast_slug, - roaster_slug: record.roaster_slug, - } - } - fn build_where_clause(filter: &BagFilter) -> Option { let mut conditions = Vec::new(); @@ -130,7 +96,7 @@ impl BagRepository for SqlBagRepository { .await .map_err(|err| RepositoryError::unexpected(err.to_string()))?; - Ok(Self::to_domain(record)) + Ok(record.into()) } async fn get(&self, id: BagId) -> Result { @@ -147,7 +113,7 @@ impl BagRepository for SqlBagRepository { .map_err(|err| RepositoryError::unexpected(err.to_string()))? .ok_or(RepositoryError::NotFound)?; - Ok(Self::to_domain(record)) + Ok(record.into()) } async fn get_with_roast(&self, id: BagId) -> Result { @@ -160,7 +126,7 @@ impl BagRepository for SqlBagRepository { .map_err(|err| RepositoryError::unexpected(err.to_string()))? .ok_or(RepositoryError::NotFound)?; - Ok(Self::to_domain_with_roast(record)) + Ok(record.into()) } async fn list( @@ -197,7 +163,7 @@ impl BagRepository for SqlBagRepository { &count_query, &order_clause, sf.as_ref(), - |record| Ok(Self::to_domain_with_roast(record)), + |record: BagWithRoastRecord| Ok(record.into()), ) .await } @@ -233,7 +199,7 @@ impl BagRepository for SqlBagRepository { .map_err(|err| RepositoryError::unexpected(err.to_string()))? .ok_or(RepositoryError::NotFound)?; - Ok(Self::to_domain(record)) + Ok(record.into()) } async fn delete(&self, id: BagId) -> Result<(), RepositoryError> { @@ -266,6 +232,22 @@ struct BagRecord { updated_at: DateTime, } +impl From for Bag { + fn from(record: BagRecord) -> Self { + Bag { + id: BagId::new(record.id), + roast_id: RoastId::new(record.roast_id), + roast_date: record.roast_date, + amount: record.amount, + remaining: record.remaining, + closed: record.closed, + finished_at: record.finished_at, + created_at: record.created_at, + updated_at: record.updated_at, + } + } +} + #[derive(sqlx::FromRow)] struct BagWithRoastRecord { id: i64, @@ -282,3 +264,25 @@ struct BagWithRoastRecord { roaster_name: String, roaster_slug: String, } + +impl From for BagWithRoast { + fn from(record: BagWithRoastRecord) -> Self { + BagWithRoast { + bag: Bag { + id: BagId::new(record.id), + roast_id: RoastId::new(record.roast_id), + roast_date: record.roast_date, + amount: record.amount, + remaining: record.remaining, + closed: record.closed, + finished_at: record.finished_at, + created_at: record.created_at, + updated_at: record.updated_at, + }, + roast_name: record.roast_name, + roaster_name: record.roaster_name, + roast_slug: record.roast_slug, + roaster_slug: record.roaster_slug, + } + } +} diff --git a/src/infrastructure/repositories/coffee/brews.rs b/src/infrastructure/repositories/coffee/brews.rs index bbbff43..95f81af 100644 --- a/src/infrastructure/repositories/coffee/brews.rs +++ b/src/infrastructure/repositories/coffee/brews.rs @@ -33,6 +33,17 @@ const BASE_SELECT: &str = r" LEFT JOIN gear g_fp ON br.filter_paper_id = g_fp.id "; +fn decode_quick_notes(raw: Option) -> Vec { + match raw { + Some(s) if !s.is_empty() => serde_json::from_str::>(&s) + .unwrap_or_default() + .iter() + .filter_map(|v| QuickNote::from_str_value(v)) + .collect(), + _ => Vec::new(), + } +} + #[derive(Clone)] pub struct SqlBrewRepository { pool: DatabasePool, @@ -56,17 +67,6 @@ impl SqlBrewRepository { } } - fn decode_quick_notes(raw: Option) -> Vec { - match raw { - Some(s) if !s.is_empty() => serde_json::from_str::>(&s) - .unwrap_or_default() - .iter() - .filter_map(|v| QuickNote::from_str_value(v)) - .collect(), - _ => Vec::new(), - } - } - fn encode_quick_notes(notes: &[QuickNote]) -> Option { if notes.is_empty() { None @@ -82,52 +82,6 @@ impl SqlBrewRepository { } } - fn to_domain(record: BrewRecord) -> Brew { - Brew { - id: BrewId::new(record.id), - bag_id: BagId::new(record.bag_id), - coffee_weight: record.coffee_weight, - grinder_id: GearId::new(record.grinder_id), - grind_setting: record.grind_setting, - brewer_id: GearId::new(record.brewer_id), - filter_paper_id: record.filter_paper_id.map(GearId::new), - water_volume: record.water_volume, - water_temp: record.water_temp, - quick_notes: Self::decode_quick_notes(record.quick_notes), - brew_time: record.brew_time, - created_at: record.created_at, - updated_at: record.updated_at, - } - } - - fn to_domain_with_details(record: BrewWithDetailsRecord) -> BrewWithDetails { - BrewWithDetails { - brew: Brew { - id: BrewId::new(record.id), - bag_id: BagId::new(record.bag_id), - coffee_weight: record.coffee_weight, - grinder_id: GearId::new(record.grinder_id), - grind_setting: record.grind_setting, - brewer_id: GearId::new(record.brewer_id), - filter_paper_id: record.filter_paper_id.map(GearId::new), - water_volume: record.water_volume, - water_temp: record.water_temp, - quick_notes: Self::decode_quick_notes(record.quick_notes), - brew_time: record.brew_time, - created_at: record.created_at, - updated_at: record.updated_at, - }, - roast_name: record.roast_name, - roaster_name: record.roaster_name, - roast_slug: record.roast_slug, - roaster_slug: record.roaster_slug, - grinder_name: record.grinder_name, - grinder_model: record.grinder_model, - brewer_name: record.brewer_name, - filter_paper_name: record.filter_paper_name, - } - } - fn build_where_clause(filter: &BrewFilter) -> Option { // SAFETY: Direct interpolation is safe here because `bag_id` is an i64 from a typed wrapper. filter @@ -201,7 +155,7 @@ impl BrewRepository for SqlBrewRepository { .await .map_err(|err| RepositoryError::unexpected(err.to_string()))?; - Ok(Self::to_domain(record)) + Ok(record.into()) } async fn get(&self, id: BrewId) -> Result { @@ -218,7 +172,7 @@ impl BrewRepository for SqlBrewRepository { .map_err(|err| RepositoryError::unexpected(err.to_string()))? .ok_or(RepositoryError::NotFound)?; - Ok(Self::to_domain(record)) + Ok(record.into()) } async fn get_with_details(&self, id: BrewId) -> Result { @@ -231,7 +185,7 @@ impl BrewRepository for SqlBrewRepository { .map_err(|err| RepositoryError::unexpected(err.to_string()))? .ok_or(RepositoryError::NotFound)?; - Ok(Self::to_domain_with_details(record)) + Ok(record.into()) } async fn list( @@ -271,7 +225,7 @@ impl BrewRepository for SqlBrewRepository { &count_query, &order_clause, sf.as_ref(), - |record| Ok(Self::to_domain_with_details(record)), + |record: BrewWithDetailsRecord| Ok(record.into()), ) .await } @@ -339,7 +293,7 @@ impl BrewRepository for SqlBrewRepository { .map_err(|err| RepositoryError::unexpected(err.to_string()))? .ok_or(RepositoryError::NotFound)?; - Ok(Self::to_domain(record)) + Ok(record.into()) } async fn delete(&self, id: BrewId) -> Result<(), RepositoryError> { @@ -376,6 +330,26 @@ struct BrewRecord { updated_at: DateTime, } +impl From for Brew { + fn from(record: BrewRecord) -> Self { + Brew { + id: BrewId::new(record.id), + bag_id: BagId::new(record.bag_id), + coffee_weight: record.coffee_weight, + grinder_id: GearId::new(record.grinder_id), + grind_setting: record.grind_setting, + brewer_id: GearId::new(record.brewer_id), + filter_paper_id: record.filter_paper_id.map(GearId::new), + water_volume: record.water_volume, + water_temp: record.water_temp, + quick_notes: decode_quick_notes(record.quick_notes), + brew_time: record.brew_time, + created_at: record.created_at, + updated_at: record.updated_at, + } + } +} + #[derive(sqlx::FromRow)] struct BrewWithDetailsRecord { id: i64, @@ -400,3 +374,33 @@ struct BrewWithDetailsRecord { brewer_name: String, filter_paper_name: Option, } + +impl From for BrewWithDetails { + fn from(record: BrewWithDetailsRecord) -> Self { + BrewWithDetails { + brew: Brew { + id: BrewId::new(record.id), + bag_id: BagId::new(record.bag_id), + coffee_weight: record.coffee_weight, + grinder_id: GearId::new(record.grinder_id), + grind_setting: record.grind_setting, + brewer_id: GearId::new(record.brewer_id), + filter_paper_id: record.filter_paper_id.map(GearId::new), + water_volume: record.water_volume, + water_temp: record.water_temp, + quick_notes: decode_quick_notes(record.quick_notes), + brew_time: record.brew_time, + created_at: record.created_at, + updated_at: record.updated_at, + }, + roast_name: record.roast_name, + roaster_name: record.roaster_name, + roast_slug: record.roast_slug, + roaster_slug: record.roaster_slug, + grinder_name: record.grinder_name, + grinder_model: record.grinder_model, + brewer_name: record.brewer_name, + filter_paper_name: record.filter_paper_name, + } + } +} diff --git a/src/infrastructure/repositories/coffee/cafes.rs b/src/infrastructure/repositories/coffee/cafes.rs index 14a6060..c837c70 100644 --- a/src/infrastructure/repositories/coffee/cafes.rs +++ b/src/infrastructure/repositories/coffee/cafes.rs @@ -33,34 +33,6 @@ impl SqlCafeRepository { CafeSortKey::Country => format!("LOWER(country) {dir_sql}, LOWER(name) ASC"), } } - - fn into_domain(record: CafeRecord) -> Cafe { - let CafeRecord { - id, - name, - slug, - city, - country, - latitude, - longitude, - website, - created_at, - updated_at, - } = record; - - Cafe { - id: CafeId::from(id), - name, - slug, - city, - country, - latitude, - longitude, - website, - created_at, - updated_at, - } - } } #[async_trait] @@ -96,7 +68,7 @@ impl CafeRepository for SqlCafeRepository { RepositoryError::unexpected(err.to_string()) })?; - Ok(Self::into_domain(record)) + Ok(record.into()) } async fn get(&self, id: CafeId) -> Result { @@ -109,7 +81,7 @@ impl CafeRepository for SqlCafeRepository { .map_err(|err| RepositoryError::unexpected(err.to_string()))?; match record { - Some(record) => Ok(Self::into_domain(record)), + Some(record) => Ok(record.into()), None => Err(RepositoryError::NotFound), } } @@ -124,7 +96,7 @@ impl CafeRepository for SqlCafeRepository { .map_err(|err| RepositoryError::unexpected(err.to_string()))?; match record { - Some(record) => Ok(Self::into_domain(record)), + Some(record) => Ok(record.into()), None => Err(RepositoryError::NotFound), } } @@ -148,7 +120,7 @@ impl CafeRepository for SqlCafeRepository { count_query, &order_clause, sf.as_ref(), - |record| Ok(Self::into_domain(record)), + |record: CafeRecord| Ok(record.into()), ) .await } @@ -210,3 +182,20 @@ struct CafeRecord { created_at: DateTime, updated_at: DateTime, } + +impl From for Cafe { + fn from(record: CafeRecord) -> Self { + Cafe { + id: CafeId::from(record.id), + name: record.name, + slug: record.slug, + city: record.city, + country: record.country, + latitude: record.latitude, + longitude: record.longitude, + website: record.website, + created_at: record.created_at, + updated_at: record.updated_at, + } + } +} diff --git a/src/infrastructure/repositories/coffee/cups.rs b/src/infrastructure/repositories/coffee/cups.rs index 4a94b73..e21f247 100644 --- a/src/infrastructure/repositories/coffee/cups.rs +++ b/src/infrastructure/repositories/coffee/cups.rs @@ -57,35 +57,6 @@ impl SqlCupRepository { } } - fn to_domain(record: CupRecord) -> Cup { - Cup { - id: CupId::new(record.id), - roast_id: RoastId::new(record.roast_id), - cafe_id: CafeId::new(record.cafe_id), - created_at: record.created_at, - updated_at: record.updated_at, - } - } - - fn to_domain_with_details(record: CupWithDetailsRecord) -> CupWithDetails { - CupWithDetails { - cup: Cup { - id: CupId::new(record.id), - roast_id: RoastId::new(record.roast_id), - cafe_id: CafeId::new(record.cafe_id), - created_at: record.created_at, - updated_at: record.updated_at, - }, - roast_name: record.roast_name, - roaster_name: record.roaster_name, - roast_slug: record.roast_slug, - roaster_slug: record.roaster_slug, - cafe_name: record.cafe_name, - cafe_slug: record.cafe_slug, - cafe_city: record.cafe_city, - } - } - fn build_where_clause(filter: &CupFilter) -> Option { let mut conditions = Vec::new(); @@ -121,7 +92,7 @@ impl CupRepository for SqlCupRepository { .await .map_err(|err| RepositoryError::unexpected(err.to_string()))?; - Ok(Self::to_domain(record)) + Ok(record.into()) } async fn get(&self, id: CupId) -> Result { @@ -134,7 +105,7 @@ impl CupRepository for SqlCupRepository { .map_err(|err| RepositoryError::unexpected(err.to_string()))?; match record { - Some(record) => Ok(Self::to_domain(record)), + Some(record) => Ok(record.into()), None => Err(RepositoryError::NotFound), } } @@ -149,7 +120,7 @@ impl CupRepository for SqlCupRepository { .map_err(|err| RepositoryError::unexpected(err.to_string()))? .ok_or(RepositoryError::NotFound)?; - Ok(Self::to_domain_with_details(record)) + Ok(record.into()) } async fn list( @@ -189,7 +160,7 @@ impl CupRepository for SqlCupRepository { &count_query, &order_clause, sf.as_ref(), - |record| Ok(Self::to_domain_with_details(record)), + |record: CupWithDetailsRecord| Ok(record.into()), ) .await } @@ -226,7 +197,7 @@ impl CupRepository for SqlCupRepository { .map_err(|err| RepositoryError::unexpected(err.to_string()))? .ok_or(RepositoryError::NotFound)?; - Ok(Self::to_domain(record)) + Ok(record.into()) } async fn delete(&self, id: CupId) -> Result<(), RepositoryError> { @@ -253,6 +224,18 @@ struct CupRecord { updated_at: DateTime, } +impl From for Cup { + fn from(record: CupRecord) -> Self { + Cup { + id: CupId::new(record.id), + roast_id: RoastId::new(record.roast_id), + cafe_id: CafeId::new(record.cafe_id), + created_at: record.created_at, + updated_at: record.updated_at, + } + } +} + #[derive(sqlx::FromRow)] struct CupWithDetailsRecord { id: i64, @@ -268,3 +251,24 @@ struct CupWithDetailsRecord { cafe_slug: String, cafe_city: String, } + +impl From for CupWithDetails { + fn from(record: CupWithDetailsRecord) -> Self { + CupWithDetails { + cup: Cup { + id: CupId::new(record.id), + roast_id: RoastId::new(record.roast_id), + cafe_id: CafeId::new(record.cafe_id), + created_at: record.created_at, + updated_at: record.updated_at, + }, + roast_name: record.roast_name, + roaster_name: record.roaster_name, + roast_slug: record.roast_slug, + roaster_slug: record.roaster_slug, + cafe_name: record.cafe_name, + cafe_slug: record.cafe_slug, + cafe_city: record.cafe_city, + } + } +} diff --git a/src/infrastructure/repositories/coffee/gear.rs b/src/infrastructure/repositories/coffee/gear.rs index cecd883..105935e 100644 --- a/src/infrastructure/repositories/coffee/gear.rs +++ b/src/infrastructure/repositories/coffee/gear.rs @@ -36,21 +36,6 @@ impl SqlGearRepository { } } - fn to_domain(record: GearRecord) -> Result { - let category = GearCategory::from_str(&record.category).map_err(|()| { - RepositoryError::unexpected(format!("invalid category: {}", record.category)) - })?; - - Ok(Gear { - id: GearId::new(record.id), - category, - make: record.make, - model: record.model, - created_at: record.created_at, - updated_at: record.updated_at, - }) - } - fn build_where_clause(filter: &GearFilter) -> Option<&'static str> { filter.category.as_ref().map(|category| match category { GearCategory::Grinder => "category = 'grinder'", @@ -80,7 +65,7 @@ impl GearRepository for SqlGearRepository { .await .map_err(|err| RepositoryError::unexpected(err.to_string()))?; - Self::to_domain(record) + record.try_into() } async fn get(&self, id: GearId) -> Result { @@ -97,7 +82,7 @@ impl GearRepository for SqlGearRepository { .map_err(|err| RepositoryError::unexpected(err.to_string()))? .ok_or(RepositoryError::NotFound)?; - Self::to_domain(record) + record.try_into() } async fn list( @@ -134,7 +119,7 @@ impl GearRepository for SqlGearRepository { &count_query, &order_clause, sf.as_ref(), - Self::to_domain, + |record: GearRecord| record.try_into(), ) .await } @@ -159,7 +144,7 @@ impl GearRepository for SqlGearRepository { .map_err(|err| RepositoryError::unexpected(err.to_string()))? .ok_or(RepositoryError::NotFound)?; - Self::to_domain(record) + record.try_into() } async fn delete(&self, id: GearId) -> Result<(), RepositoryError> { @@ -188,3 +173,22 @@ struct GearRecord { created_at: DateTime, updated_at: DateTime, } + +impl TryFrom for Gear { + type Error = RepositoryError; + + fn try_from(record: GearRecord) -> Result { + let category = GearCategory::from_str(&record.category).map_err(|()| { + RepositoryError::unexpected(format!("invalid category: {}", record.category)) + })?; + + Ok(Gear { + id: GearId::new(record.id), + category, + make: record.make, + model: record.model, + created_at: record.created_at, + updated_at: record.updated_at, + }) + } +} diff --git a/src/infrastructure/repositories/coffee/roasters.rs b/src/infrastructure/repositories/coffee/roasters.rs index 81baece..88c31f4 100644 --- a/src/infrastructure/repositories/coffee/roasters.rs +++ b/src/infrastructure/repositories/coffee/roasters.rs @@ -33,28 +33,6 @@ impl SqlRoasterRepository { RoasterSortKey::City => format!("LOWER(COALESCE(city, '')) {dir_sql}, LOWER(name) ASC"), } } - - fn into_domain(record: RoasterRecord) -> Roaster { - let RoasterRecord { - id, - name, - slug, - country, - city, - homepage, - created_at, - } = record; - - Roaster { - id: RoasterId::from(id), - name, - slug, - country, - city, - homepage, - created_at, - } - } } #[async_trait] @@ -87,7 +65,7 @@ impl RoasterRepository for SqlRoasterRepository { RepositoryError::unexpected(err.to_string()) })?; - Ok(Self::into_domain(record)) + Ok(record.into()) } async fn get(&self, id: RoasterId) -> Result { @@ -100,7 +78,7 @@ impl RoasterRepository for SqlRoasterRepository { .map_err(|err| RepositoryError::unexpected(err.to_string()))?; match record { - Some(record) => Ok(Self::into_domain(record)), + Some(record) => Ok(record.into()), None => Err(RepositoryError::NotFound), } } @@ -115,7 +93,7 @@ impl RoasterRepository for SqlRoasterRepository { .map_err(|err| RepositoryError::unexpected(err.to_string()))?; match record { - Some(record) => Ok(Self::into_domain(record)), + Some(record) => Ok(record.into()), None => Err(RepositoryError::NotFound), } } @@ -140,7 +118,7 @@ impl RoasterRepository for SqlRoasterRepository { count_query, &order_clause, sf.as_ref(), - |record| Ok(Self::into_domain(record)), + |record: RoasterRecord| Ok(record.into()), ) .await } @@ -206,3 +184,17 @@ struct RoasterRecord { homepage: Option, created_at: DateTime, } + +impl From for Roaster { + fn from(record: RoasterRecord) -> Self { + Roaster { + id: RoasterId::from(record.id), + name: record.name, + slug: record.slug, + country: record.country, + city: record.city, + homepage: record.homepage, + created_at: record.created_at, + } + } +} diff --git a/src/infrastructure/repositories/coffee/roasts.rs b/src/infrastructure/repositories/coffee/roasts.rs index eda4e7f..e0497cd 100644 --- a/src/infrastructure/repositories/coffee/roasts.rs +++ b/src/infrastructure/repositories/coffee/roasts.rs @@ -104,7 +104,7 @@ impl RoastRepository for SqlRoastRepository { map_insert_error(err, "unknown roaster reference") })?; - record.into_roast() + record.try_into() } async fn get(&self, id: RoastId) -> Result { @@ -115,7 +115,7 @@ impl RoastRepository for SqlRoastRepository { .fetch_optional(&self.pool) .await .map_err(|err| RepositoryError::unexpected(err.to_string()))? - .map(RoastRecord::into_roast) + .map(Roast::try_from) .transpose()? .ok_or(RepositoryError::NotFound) } @@ -131,7 +131,7 @@ impl RoastRepository for SqlRoastRepository { .fetch_optional(&self.pool) .await .map_err(|err| RepositoryError::unexpected(err.to_string()))? - .map(RoastWithRoasterRecord::into_with_roaster) + .map(RoastWithRoaster::try_from) .transpose()? .ok_or(RepositoryError::NotFound) } @@ -149,7 +149,7 @@ impl RoastRepository for SqlRoastRepository { .fetch_optional(&self.pool) .await .map_err(|err| RepositoryError::unexpected(err.to_string()))? - .map(RoastRecord::into_roast) + .map(Roast::try_from) .transpose()? .ok_or(RepositoryError::NotFound) } @@ -184,7 +184,7 @@ impl RoastRepository for SqlRoastRepository { count_query, &order_clause, sf.as_ref(), - |record: RoastWithRoasterRecord| record.into_with_roaster(), + |record: RoastWithRoasterRecord| record.try_into(), ) .await } @@ -203,7 +203,7 @@ impl RoastRepository for SqlRoastRepository { records .into_iter() - .map(RoastWithRoasterRecord::into_with_roaster) + .map(RoastWithRoaster::try_from) .collect() } @@ -307,9 +307,11 @@ struct RoastRecord { created_at: DateTime, } -impl RoastRecord { - fn into_roast(self) -> Result { - let tasting_notes = match self.tasting_notes { +impl TryFrom for Roast { + type Error = RepositoryError; + + fn try_from(record: RoastRecord) -> Result { + let tasting_notes = match record.tasting_notes { Some(raw) => from_str::>(&raw).map_err(|err| { RepositoryError::unexpected(format!("failed to decode tasting notes: {err}")) })?, @@ -317,16 +319,16 @@ impl RoastRecord { }; Ok(Roast { - id: RoastId::from(self.id), - roaster_id: RoasterId::from(self.roaster_id), - name: self.name, - slug: self.slug, - origin: self.origin, - region: self.region, - producer: self.producer, - process: self.process, + id: RoastId::from(record.id), + roaster_id: RoasterId::from(record.roaster_id), + name: record.name, + slug: record.slug, + origin: record.origin, + region: record.region, + producer: record.producer, + process: record.process, tasting_notes, - created_at: self.created_at, + created_at: record.created_at, }) } } @@ -347,9 +349,11 @@ struct RoastWithRoasterRecord { roaster_slug: String, } -impl RoastWithRoasterRecord { - fn into_with_roaster(self) -> Result { - let tasting_notes = match self.tasting_notes { +impl TryFrom for RoastWithRoaster { + type Error = RepositoryError; + + fn try_from(record: RoastWithRoasterRecord) -> Result { + let tasting_notes = match record.tasting_notes { Some(raw) => from_str::>(&raw).map_err(|err| { RepositoryError::unexpected(format!("failed to decode tasting notes: {err}")) })?, @@ -358,19 +362,19 @@ impl RoastWithRoasterRecord { Ok(RoastWithRoaster { roast: Roast { - id: RoastId::from(self.id), - roaster_id: RoasterId::from(self.roaster_id), - name: self.name, - slug: self.slug, - origin: self.origin, - region: self.region, - producer: self.producer, - process: self.process, + id: RoastId::from(record.id), + roaster_id: RoasterId::from(record.roaster_id), + name: record.name, + slug: record.slug, + origin: record.origin, + region: record.region, + producer: record.producer, + process: record.process, tasting_notes, - created_at: self.created_at, + created_at: record.created_at, }, - roaster_name: self.roaster_name, - roaster_slug: self.roaster_slug, + roaster_name: record.roaster_name, + roaster_slug: record.roaster_slug, }) } }