From 344bcf34af945dde8c04b3f679fa9bf63d985692 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Fri, 15 May 2026 01:54:32 +0200 Subject: [PATCH] refactor(domain): move DB string conversions out of domain enums --- crates/adapters/postgres-search/src/lib.rs | 14 ++++++++-- crates/adapters/postgres/src/feed.rs | 14 ++++++++-- crates/adapters/postgres/src/follow.rs | 25 +++++++++++++++-- crates/adapters/postgres/src/notification.rs | 29 ++++++++++++++++++-- crates/adapters/postgres/src/thought.rs | 27 ++++++++++++++++-- crates/application/src/use_cases/thoughts.rs | 11 ++++---- crates/domain/src/models/notification.rs | 20 -------------- crates/domain/src/models/social.rs | 16 ----------- crates/domain/src/models/thought.rs | 18 ------------ crates/presentation/src/handlers/feed.rs | 12 +++++++- crates/presentation/src/handlers/thoughts.rs | 12 +++++++- 11 files changed, 124 insertions(+), 74 deletions(-) diff --git a/crates/adapters/postgres-search/src/lib.rs b/crates/adapters/postgres-search/src/lib.rs index 4eca11f..e384f15 100644 --- a/crates/adapters/postgres-search/src/lib.rs +++ b/crates/adapters/postgres-search/src/lib.rs @@ -1,6 +1,16 @@ use async_trait::async_trait; use chrono::{DateTime, Utc}; -use domain::models::thought::Visibility; + +fn visibility_from_str(s: &str) -> domain::models::thought::Visibility { + use domain::models::thought::Visibility; + match s { + "followers" => Visibility::Followers, + "unlisted" => Visibility::Unlisted, + "direct" => Visibility::Direct, + _ => Visibility::Public, + } +} + use domain::{ errors::DomainError, models::{ @@ -81,7 +91,7 @@ fn row_to_entry(r: FeedRow) -> FeedEntry { in_reply_to_id: r.in_reply_to_id.map(ThoughtId::from_uuid), in_reply_to_url: r.in_reply_to_url, ap_id: r.t_ap_id, - visibility: Visibility::from_db_str(&r.visibility), + visibility: visibility_from_str(&r.visibility), content_warning: r.content_warning, sensitive: r.sensitive, local: r.t_local, diff --git a/crates/adapters/postgres/src/feed.rs b/crates/adapters/postgres/src/feed.rs index 2470db0..a809597 100644 --- a/crates/adapters/postgres/src/feed.rs +++ b/crates/adapters/postgres/src/feed.rs @@ -1,6 +1,16 @@ use async_trait::async_trait; use chrono::{DateTime, Utc}; -use domain::models::thought::Visibility; + +fn visibility_from_str(s: &str) -> domain::models::thought::Visibility { + use domain::models::thought::Visibility; + match s { + "followers" => Visibility::Followers, + "unlisted" => Visibility::Unlisted, + "direct" => Visibility::Direct, + _ => Visibility::Public, + } +} + use domain::{ errors::DomainError, models::{ @@ -95,7 +105,7 @@ fn row_to_entry(r: FeedRow) -> FeedEntry { in_reply_to_id: r.in_reply_to_id.map(ThoughtId::from_uuid), in_reply_to_url: r.in_reply_to_url, ap_id: r.t_ap_id, - visibility: Visibility::from_db_str(&r.visibility), + visibility: visibility_from_str(&r.visibility), content_warning: r.content_warning, sensitive: r.sensitive, local: r.t_local, diff --git a/crates/adapters/postgres/src/follow.rs b/crates/adapters/postgres/src/follow.rs index dcb7075..a78270a 100644 --- a/crates/adapters/postgres/src/follow.rs +++ b/crates/adapters/postgres/src/follow.rs @@ -1,5 +1,24 @@ use async_trait::async_trait; use chrono::{DateTime, Utc}; + +fn follow_state_from_str(s: &str) -> domain::models::social::FollowState { + use domain::models::social::FollowState; + match s { + "pending" => FollowState::Pending, + "rejected" => FollowState::Rejected, + _ => FollowState::Accepted, + } +} + +fn follow_state_as_str(state: &domain::models::social::FollowState) -> &'static str { + use domain::models::social::FollowState; + match state { + FollowState::Pending => "pending", + FollowState::Accepted => "accepted", + FollowState::Rejected => "rejected", + } +} + use domain::{ errors::DomainError, models::{ @@ -31,7 +50,7 @@ impl FollowRepository for PgFollowRepository { ) .bind(f.follower_id.as_uuid()) .bind(f.following_id.as_uuid()) - .bind(f.state.as_str()) + .bind(follow_state_as_str(&f.state)) .bind(&f.ap_id) .bind(f.created_at) .execute(&self.pool) @@ -77,7 +96,7 @@ impl FollowRepository for PgFollowRepository { .map(|o| o.map(|r| Follow { follower_id: UserId::from_uuid(r.follower_id), following_id: UserId::from_uuid(r.following_id), - state: FollowState::from_db_str(&r.state), + state: follow_state_from_str(&r.state), ap_id: r.ap_id, created_at: r.created_at, })) @@ -92,7 +111,7 @@ impl FollowRepository for PgFollowRepository { sqlx::query("UPDATE follows SET state=$3 WHERE follower_id=$1 AND following_id=$2") .bind(follower_id.as_uuid()) .bind(following_id.as_uuid()) - .bind(state.as_str()) + .bind(follow_state_as_str(state)) .execute(&self.pool) .await .map_err(|e| DomainError::Internal(e.to_string())) diff --git a/crates/adapters/postgres/src/notification.rs b/crates/adapters/postgres/src/notification.rs index 4c4e199..016b302 100644 --- a/crates/adapters/postgres/src/notification.rs +++ b/crates/adapters/postgres/src/notification.rs @@ -1,10 +1,33 @@ use async_trait::async_trait; use chrono::{DateTime, Utc}; + +fn notif_type_from_str(s: &str) -> domain::models::notification::NotificationType { + use domain::models::notification::NotificationType; + match s { + "like" => NotificationType::Like, + "boost" => NotificationType::Boost, + "follow" => NotificationType::Follow, + "mention" => NotificationType::Mention, + _ => NotificationType::Reply, + } +} + +fn notif_type_as_str(t: &domain::models::notification::NotificationType) -> &'static str { + use domain::models::notification::NotificationType; + match t { + NotificationType::Like => "like", + NotificationType::Boost => "boost", + NotificationType::Follow => "follow", + NotificationType::Mention => "mention", + NotificationType::Reply => "reply", + } +} + use domain::{ errors::DomainError, models::{ feed::{PageParams, Paginated}, - notification::{Notification, NotificationType}, + notification::Notification, }, ports::NotificationRepository, value_objects::{NotificationId, ThoughtId, UserId}, @@ -26,7 +49,7 @@ impl NotificationRepository for PgNotificationRepository { sqlx::query( "INSERT INTO notifications(id,user_id,type,from_user_id,thought_id,read,created_at) VALUES($1,$2,$3,$4,$5,$6,$7)" ) - .bind(n.id.as_uuid()).bind(n.user_id.as_uuid()).bind(n.notification_type.as_str()) + .bind(n.id.as_uuid()).bind(n.user_id.as_uuid()).bind(notif_type_as_str(&n.notification_type)) .bind(n.from_user_id.as_ref().map(|u| u.as_uuid())) .bind(n.thought_id.as_ref().map(|t| t.as_uuid())) .bind(n.read).bind(n.created_at) @@ -62,7 +85,7 @@ impl NotificationRepository for PgNotificationRepository { .map(|r| Notification { id: NotificationId::from_uuid(r.id), user_id: UserId::from_uuid(r.user_id), - notification_type: NotificationType::from_db_str(&r.r#type), + notification_type: notif_type_from_str(&r.r#type), from_user_id: r.from_user_id.map(UserId::from_uuid), thought_id: r.thought_id.map(ThoughtId::from_uuid), read: r.read, diff --git a/crates/adapters/postgres/src/thought.rs b/crates/adapters/postgres/src/thought.rs index a21ac8e..70e4c74 100644 --- a/crates/adapters/postgres/src/thought.rs +++ b/crates/adapters/postgres/src/thought.rs @@ -1,10 +1,31 @@ use async_trait::async_trait; use chrono::{DateTime, Utc}; + +fn visibility_from_str(s: &str) -> domain::models::thought::Visibility { + use domain::models::thought::Visibility; + match s { + "followers" => Visibility::Followers, + "unlisted" => Visibility::Unlisted, + "direct" => Visibility::Direct, + _ => Visibility::Public, + } +} + +fn visibility_as_str(v: &domain::models::thought::Visibility) -> &'static str { + use domain::models::thought::Visibility; + match v { + Visibility::Public => "public", + Visibility::Followers => "followers", + Visibility::Unlisted => "unlisted", + Visibility::Direct => "direct", + } +} + use domain::{ errors::DomainError, models::{ feed::{PageParams, Paginated}, - thought::{Thought, Visibility}, + thought::Thought, }, ports::ThoughtRepository, value_objects::{Content, ThoughtId, UserId}, @@ -45,7 +66,7 @@ impl From for Thought { in_reply_to_id: r.in_reply_to_id.map(ThoughtId::from_uuid), in_reply_to_url: r.in_reply_to_url, ap_id: r.ap_id, - visibility: Visibility::from_db_str(&r.visibility), + visibility: visibility_from_str(&r.visibility), content_warning: r.content_warning, sensitive: r.sensitive, local: r.local, @@ -72,7 +93,7 @@ impl ThoughtRepository for PgThoughtRepository { .bind(t.in_reply_to_id.as_ref().map(|x| x.as_uuid())) .bind(&t.in_reply_to_url) .bind(&t.ap_id) - .bind(t.visibility.as_str()) + .bind(visibility_as_str(&t.visibility)) .bind(&t.content_warning) .bind(t.sensitive) .bind(t.local) diff --git a/crates/application/src/use_cases/thoughts.rs b/crates/application/src/use_cases/thoughts.rs index 38abdfe..406fc4d 100644 --- a/crates/application/src/use_cases/thoughts.rs +++ b/crates/application/src/use_cases/thoughts.rs @@ -57,11 +57,12 @@ pub async fn create_thought( input: CreateThoughtInput, ) -> Result { let content = Content::new_local(input.content)?; - let visibility = input - .visibility - .as_deref() - .map(Visibility::from_db_str) - .unwrap_or(Visibility::Public); + let visibility = match input.visibility.as_deref() { + Some("followers") => Visibility::Followers, + Some("unlisted") => Visibility::Unlisted, + Some("direct") => Visibility::Direct, + _ => Visibility::Public, + }; let thought = Thought::new_local( ThoughtId::new(), input.user_id, diff --git a/crates/domain/src/models/notification.rs b/crates/domain/src/models/notification.rs index 4483d4e..7e50b1c 100644 --- a/crates/domain/src/models/notification.rs +++ b/crates/domain/src/models/notification.rs @@ -9,26 +9,6 @@ pub enum NotificationType { Mention, Reply, } -impl NotificationType { - pub fn from_db_str(s: &str) -> Self { - match s { - "like" => Self::Like, - "boost" => Self::Boost, - "follow" => Self::Follow, - "mention" => Self::Mention, - _ => Self::Reply, - } - } - pub fn as_str(&self) -> &str { - match self { - Self::Like => "like", - Self::Boost => "boost", - Self::Follow => "follow", - Self::Mention => "mention", - Self::Reply => "reply", - } - } -} #[derive(Debug, Clone)] pub struct Notification { diff --git a/crates/domain/src/models/social.rs b/crates/domain/src/models/social.rs index ac498d6..12b62bc 100644 --- a/crates/domain/src/models/social.rs +++ b/crates/domain/src/models/social.rs @@ -25,22 +25,6 @@ pub enum FollowState { Accepted, Rejected, } -impl FollowState { - pub fn from_db_str(s: &str) -> Self { - match s { - "pending" => Self::Pending, - "rejected" => Self::Rejected, - _ => Self::Accepted, - } - } - pub fn as_str(&self) -> &str { - match self { - Self::Pending => "pending", - Self::Accepted => "accepted", - Self::Rejected => "rejected", - } - } -} #[derive(Debug, Clone)] pub struct Follow { diff --git a/crates/domain/src/models/thought.rs b/crates/domain/src/models/thought.rs index cc63238..bb3a173 100644 --- a/crates/domain/src/models/thought.rs +++ b/crates/domain/src/models/thought.rs @@ -8,24 +8,6 @@ pub enum Visibility { Unlisted, Direct, } -impl Visibility { - pub fn from_db_str(s: &str) -> Self { - match s { - "followers" => Self::Followers, - "unlisted" => Self::Unlisted, - "direct" => Self::Direct, - _ => Self::Public, - } - } - pub fn as_str(&self) -> &str { - match self { - Self::Public => "public", - Self::Followers => "followers", - Self::Unlisted => "unlisted", - Self::Direct => "direct", - } - } -} #[derive(Debug, Clone)] pub struct Thought { diff --git a/crates/presentation/src/handlers/feed.rs b/crates/presentation/src/handlers/feed.rs index 61a0226..5253897 100644 --- a/crates/presentation/src/handlers/feed.rs +++ b/crates/presentation/src/handlers/feed.rs @@ -21,13 +21,23 @@ use axum::{ use domain::models::feed::PageParams; use domain::value_objects::UserId; +fn visibility_as_str(v: &domain::models::thought::Visibility) -> &'static str { + use domain::models::thought::Visibility; + match v { + Visibility::Public => "public", + Visibility::Followers => "followers", + Visibility::Unlisted => "unlisted", + Visibility::Direct => "direct", + } +} + pub fn to_thought_response(e: &domain::models::feed::FeedEntry) -> ThoughtResponse { ThoughtResponse { id: e.thought.id.as_uuid(), content: e.thought.content.as_str().to_string(), author: to_user_response(&e.author), in_reply_to_id: e.thought.in_reply_to_id.as_ref().map(|id| id.as_uuid()), - visibility: e.thought.visibility.as_str().to_string(), + visibility: visibility_as_str(&e.thought.visibility).to_string(), content_warning: e.thought.content_warning.clone(), sensitive: e.thought.sensitive, like_count: e.like_count, diff --git a/crates/presentation/src/handlers/thoughts.rs b/crates/presentation/src/handlers/thoughts.rs index 73085a7..21467ca 100644 --- a/crates/presentation/src/handlers/thoughts.rs +++ b/crates/presentation/src/handlers/thoughts.rs @@ -20,6 +20,16 @@ use axum::{ use domain::value_objects::ThoughtId; use uuid::Uuid; +fn visibility_as_str(v: &domain::models::thought::Visibility) -> &'static str { + use domain::models::thought::Visibility; + match v { + Visibility::Public => "public", + Visibility::Followers => "followers", + Visibility::Unlisted => "unlisted", + Visibility::Direct => "direct", + } +} + fn thought_to_json( t: &domain::models::thought::Thought, author: &domain::models::user::User, @@ -32,7 +42,7 @@ fn thought_to_json( "content": t.content.as_str(), "author": to_user_response(author), "replyToId": t.in_reply_to_id.as_ref().map(|x| x.as_uuid()), - "visibility": t.visibility.as_str(), + "visibility": visibility_as_str(&t.visibility), "contentWarning": t.content_warning, "sensitive": t.sensitive, "likeCount": like_count,