diff --git a/backend/feature/post/src/adapter/delivery/post_controller.rs b/backend/feature/post/src/adapter/delivery/post_controller.rs index ca2ef5b..27737d3 100644 --- a/backend/feature/post/src/adapter/delivery/post_controller.rs +++ b/backend/feature/post/src/adapter/delivery/post_controller.rs @@ -15,8 +15,9 @@ use crate::{ create_label_use_case::CreateLabelUseCase, create_post_use_case::CreatePostUseCase, get_all_labels_use_case::GetAllLabelsUseCase, get_all_post_info_use_case::GetAllPostInfoUseCase, - get_full_post_use_case::GetFullPostUseCase, update_label_use_case::UpdateLabelUseCase, - update_post_use_case::UpdatePostUseCase, + get_post_by_id_use_case::GetPostByIdUseCase, + get_post_by_sementic_id_use_case::GetPostBySemanticIdUseCase, + update_label_use_case::UpdateLabelUseCase, update_post_use_case::UpdatePostUseCase, }, }, }; @@ -34,9 +35,9 @@ pub trait PostController: Send + Sync { user_id: Option, ) -> Result, PostError>; - async fn get_post_by_id( + async fn get_post_by_id_or_semantic_id( &self, - id: i32, + id_or_semantic_id: &str, user_id: Option, ) -> Result; @@ -69,7 +70,8 @@ pub trait PostController: Send + Sync { pub struct PostControllerImpl { get_all_post_info_use_case: Arc, - get_full_post_use_case: Arc, + get_post_by_id_use_case: Arc, + get_post_by_semantic_id_use_case: Arc, create_post_use_case: Arc, update_post_use_case: Arc, create_label_use_case: Arc, @@ -80,7 +82,8 @@ pub struct PostControllerImpl { impl PostControllerImpl { pub fn new( get_all_post_info_use_case: Arc, - get_full_post_use_case: Arc, + get_post_by_id_use_case: Arc, + get_post_by_semantic_id_use_case: Arc, create_post_use_case: Arc, update_post_use_case: Arc, create_label_use_case: Arc, @@ -89,7 +92,8 @@ impl PostControllerImpl { ) -> Self { Self { get_all_post_info_use_case, - get_full_post_use_case, + get_post_by_id_use_case, + get_post_by_semantic_id_use_case, create_post_use_case, update_post_use_case, create_label_use_case, @@ -97,6 +101,29 @@ impl PostControllerImpl { get_all_labels_use_case, } } + + async fn get_post_by_id( + &self, + id: i32, + user_id: Option, + ) -> Result { + let result = self.get_post_by_id_use_case.execute(id, user_id).await; + + result.map(PostResponseDto::from) + } + + async fn get_post_by_semantic_id( + &self, + semantic_id: &str, + user_id: Option, + ) -> Result { + let result = self + .get_post_by_semantic_id_use_case + .execute(semantic_id, user_id) + .await; + + result.map(PostResponseDto::from) + } } #[async_trait] @@ -121,14 +148,17 @@ impl PostController for PostControllerImpl { }) } - async fn get_post_by_id( + async fn get_post_by_id_or_semantic_id( &self, - id: i32, + id_or_semantic_id: &str, user_id: Option, ) -> Result { - let result = self.get_full_post_use_case.execute(id, user_id).await; - - result.map(PostResponseDto::from) + if let Ok(id) = id_or_semantic_id.parse::() { + self.get_post_by_id(id, user_id).await + } else { + let semantic_id = id_or_semantic_id; + self.get_post_by_semantic_id(semantic_id, user_id).await + } } async fn create_label( diff --git a/backend/feature/post/src/adapter/gateway/post_db_service.rs b/backend/feature/post/src/adapter/gateway/post_db_service.rs index ff330b8..7d6e1e9 100644 --- a/backend/feature/post/src/adapter/gateway/post_db_service.rs +++ b/backend/feature/post/src/adapter/gateway/post_db_service.rs @@ -14,4 +14,5 @@ pub trait PostDbService: Send + Sync { async fn get_post_by_id(&self, id: i32) -> Result; async fn create_post(&self, post: PostMapper, label_ids: &[i32]) -> Result; async fn update_post(&self, post: PostMapper, label_ids: &[i32]) -> Result<(), PostError>; + async fn get_id_by_semantic_id(&self, semantic_id: &str) -> Result; } diff --git a/backend/feature/post/src/adapter/gateway/post_repository_impl.rs b/backend/feature/post/src/adapter/gateway/post_repository_impl.rs index 5ffc33e..a59e2ce 100644 --- a/backend/feature/post/src/adapter/gateway/post_repository_impl.rs +++ b/backend/feature/post/src/adapter/gateway/post_repository_impl.rs @@ -84,4 +84,8 @@ impl PostRepository for PostRepositoryImpl { .update_post(post_mapper, label_ids) .await } + + async fn get_id_by_semantic_id(&self, semantic_id: &str) -> Result { + self.post_db_service.get_id_by_semantic_id(semantic_id).await + } } diff --git a/backend/feature/post/src/application/gateway/post_repository.rs b/backend/feature/post/src/application/gateway/post_repository.rs index 36f910e..df568f0 100644 --- a/backend/feature/post/src/application/gateway/post_repository.rs +++ b/backend/feature/post/src/application/gateway/post_repository.rs @@ -11,4 +11,5 @@ pub trait PostRepository: Send + Sync { async fn get_post_by_id(&self, id: i32) -> Result; async fn create_post(&self, post: Post, label_ids: &[i32]) -> Result; async fn update_post(&self, post: Post, label_ids: &[i32]) -> Result<(), PostError>; + async fn get_id_by_semantic_id(&self, semantic_id: &str) -> Result; } diff --git a/backend/feature/post/src/application/use_case.rs b/backend/feature/post/src/application/use_case.rs index 88d3d50..9ac190d 100644 --- a/backend/feature/post/src/application/use_case.rs +++ b/backend/feature/post/src/application/use_case.rs @@ -2,6 +2,7 @@ pub mod create_label_use_case; pub mod create_post_use_case; pub mod get_all_labels_use_case; pub mod get_all_post_info_use_case; -pub mod get_full_post_use_case; +pub mod get_post_by_id_use_case; +pub mod get_post_by_sementic_id_use_case; pub mod update_label_use_case; pub mod update_post_use_case; diff --git a/backend/feature/post/src/application/use_case/get_full_post_use_case.rs b/backend/feature/post/src/application/use_case/get_post_by_id_use_case.rs similarity index 89% rename from backend/feature/post/src/application/use_case/get_full_post_use_case.rs rename to backend/feature/post/src/application/use_case/get_post_by_id_use_case.rs index 1a161a6..331cb9d 100644 --- a/backend/feature/post/src/application/use_case/get_full_post_use_case.rs +++ b/backend/feature/post/src/application/use_case/get_post_by_id_use_case.rs @@ -8,7 +8,7 @@ use crate::{ }; #[async_trait] -pub trait GetFullPostUseCase: Send + Sync { +pub trait GetPostByIdUseCase: Send + Sync { async fn execute(&self, id: i32, user_id: Option) -> Result; } @@ -23,7 +23,7 @@ impl GetFullPostUseCaseImpl { } #[async_trait] -impl GetFullPostUseCase for GetFullPostUseCaseImpl { +impl GetPostByIdUseCase for GetFullPostUseCaseImpl { async fn execute(&self, id: i32, user_id: Option) -> Result { let post = self.post_repository.get_post_by_id(id).await?; diff --git a/backend/feature/post/src/application/use_case/get_post_by_sementic_id_use_case.rs b/backend/feature/post/src/application/use_case/get_post_by_sementic_id_use_case.rs new file mode 100644 index 0000000..6794b64 --- /dev/null +++ b/backend/feature/post/src/application/use_case/get_post_by_sementic_id_use_case.rs @@ -0,0 +1,45 @@ +use std::sync::Arc; + +use async_trait::async_trait; + +use crate::{ + application::{ + error::post_error::PostError, gateway::post_repository::PostRepository, + use_case::get_post_by_id_use_case::GetPostByIdUseCase, + }, + domain::entity::post::Post, +}; + +#[async_trait] +pub trait GetPostBySemanticIdUseCase: Send + Sync { + async fn execute(&self, semantic_id: &str, user_id: Option) -> Result; +} + +pub struct GetPostIdBySemanticIdUseCaseImpl { + post_repository: Arc, + get_post_by_id_use_case: Arc, +} + +impl GetPostIdBySemanticIdUseCaseImpl { + pub fn new( + post_repository: Arc, + get_post_by_id_use_case: Arc, + ) -> Self { + Self { + post_repository, + get_post_by_id_use_case, + } + } +} + +#[async_trait] +impl GetPostBySemanticIdUseCase for GetPostIdBySemanticIdUseCaseImpl { + async fn execute(&self, semantic_id: &str, user_id: Option) -> Result { + let id = self + .post_repository + .get_id_by_semantic_id(semantic_id) + .await?; + + self.get_post_by_id_use_case.execute(id, user_id).await + } +} diff --git a/backend/feature/post/src/framework/db/post_db_service_impl.rs b/backend/feature/post/src/framework/db/post_db_service_impl.rs index b2e3071..fa46e0e 100644 --- a/backend/feature/post/src/framework/db/post_db_service_impl.rs +++ b/backend/feature/post/src/framework/db/post_db_service_impl.rs @@ -307,4 +307,23 @@ impl PostDbService for PostDbServiceImpl { Ok(()) } + + async fn get_id_by_semantic_id(&self, semantic_id: &str) -> Result { + let id = sqlx::query_scalar!( + r#" + SELECT id + FROM post + WHERE semantic_id = $1 AND deleted_time IS NULL + "#, + semantic_id, + ) + .fetch_optional(&self.db_pool) + .await + .map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?; + + match id { + Some(id) => Ok(id), + None => Err(PostError::NotFound), + } + } } diff --git a/backend/feature/post/src/framework/web/get_post_by_id_handler.rs b/backend/feature/post/src/framework/web/get_post_by_id_handler.rs index 16ca48c..97dacf8 100644 --- a/backend/feature/post/src/framework/web/get_post_by_id_handler.rs +++ b/backend/feature/post/src/framework/web/get_post_by_id_handler.rs @@ -11,8 +11,8 @@ use crate::{ get, path = "/post/{id}", tag = "post", - summary = "Get post by ID", - description = "Only authenticated users can access unpublished posts.", + summary = "Get post by ID or semantic ID", + description = "Only authenticated users can access unpublished posts. Accepts either numeric ID or semantic ID.", responses ( (status = 200, body = PostResponseDto), (status = 404, description = "Post not found") @@ -20,12 +20,12 @@ use crate::{ )] pub async fn get_post_by_id_handler( post_controller: web::Data, - path: web::Path, + path: web::Path, user_id: Option, ) -> impl Responder { - let id = path.into_inner(); + let id_or_semantic_id = path.into_inner(); let result = post_controller - .get_post_by_id(id, user_id.map(|id| id.get())) + .get_post_by_id_or_semantic_id(&id_or_semantic_id, user_id.map(|id| id.get())) .await; match result { diff --git a/backend/server/src/container.rs b/backend/server/src/container.rs index d8685da..07d90de 100644 --- a/backend/server/src/container.rs +++ b/backend/server/src/container.rs @@ -36,13 +36,12 @@ use post::{ }, }, application::use_case::{ - create_label_use_case::CreateLabelUseCaseImpl, - create_post_use_case::CreatePostUseCaseImpl, + create_label_use_case::CreateLabelUseCaseImpl, create_post_use_case::CreatePostUseCaseImpl, get_all_labels_use_case::GetAllLabelsUseCaseImpl, get_all_post_info_use_case::GetAllPostInfoUseCaseImpl, - get_full_post_use_case::GetFullPostUseCaseImpl, - update_label_use_case::UpdateLabelUseCaseImpl, - update_post_use_case::UpdatePostUseCaseImpl, + get_post_by_id_use_case::GetFullPostUseCaseImpl, + get_post_by_sementic_id_use_case::GetPostIdBySemanticIdUseCaseImpl, + update_label_use_case::UpdateLabelUseCaseImpl, update_post_use_case::UpdatePostUseCaseImpl, }, framework::db::{ label_db_service_impl::LabelDbServiceImpl, post_db_service_impl::PostDbServiceImpl, @@ -97,7 +96,12 @@ impl Container { let get_all_post_info_use_case = Arc::new(GetAllPostInfoUseCaseImpl::new(post_repository.clone())); - let get_full_post_use_case = Arc::new(GetFullPostUseCaseImpl::new(post_repository.clone())); + let get_post_by_id_use_case = + Arc::new(GetFullPostUseCaseImpl::new(post_repository.clone())); + let get_post_by_semantic_id_use_case = Arc::new(GetPostIdBySemanticIdUseCaseImpl::new( + post_repository.clone(), + get_post_by_id_use_case.clone(), + )); let create_post_use_case = Arc::new(CreatePostUseCaseImpl::new(post_repository.clone())); let update_post_use_case = Arc::new(UpdatePostUseCaseImpl::new(post_repository.clone())); let create_label_use_case = Arc::new(CreateLabelUseCaseImpl::new(label_repository.clone())); @@ -107,7 +111,8 @@ impl Container { let post_controller = Arc::new(PostControllerImpl::new( get_all_post_info_use_case, - get_full_post_use_case, + get_post_by_id_use_case, + get_post_by_semantic_id_use_case, create_post_use_case, update_post_use_case, create_label_use_case,