diff --git a/backend/feature/post/Cargo.toml b/backend/feature/post/Cargo.toml index 052b52c..dc63b3e 100644 --- a/backend/feature/post/Cargo.toml +++ b/backend/feature/post/Cargo.toml @@ -10,3 +10,4 @@ chrono.workspace = true log.workspace = true serde.workspace = true sqlx.workspace = true +utoipa.workspace = true diff --git a/backend/feature/post/src/adapter/delivery/color_response_dto.rs b/backend/feature/post/src/adapter/delivery/color_response_dto.rs index 0d8e1cf..274f95a 100644 --- a/backend/feature/post/src/adapter/delivery/color_response_dto.rs +++ b/backend/feature/post/src/adapter/delivery/color_response_dto.rs @@ -1,8 +1,9 @@ use serde::Serialize; +use utoipa::ToSchema; use crate::domain::entity::color::Color; -#[derive(Serialize)] +#[derive(Serialize, ToSchema)] pub struct ColorResponseDto { pub red: u8, pub green: u8, diff --git a/backend/feature/post/src/adapter/delivery/label_response_dto.rs b/backend/feature/post/src/adapter/delivery/label_response_dto.rs index 9da46d3..bd9a620 100644 --- a/backend/feature/post/src/adapter/delivery/label_response_dto.rs +++ b/backend/feature/post/src/adapter/delivery/label_response_dto.rs @@ -1,10 +1,11 @@ use serde::Serialize; +use utoipa::ToSchema; use crate::{ adapter::delivery::color_response_dto::ColorResponseDto, domain::entity::label::Label, }; -#[derive(Serialize)] +#[derive(Serialize, ToSchema)] pub struct LabelResponseDto { pub id: i32, pub name: String, diff --git a/backend/feature/post/src/adapter/delivery/post_controller.rs b/backend/feature/post/src/adapter/delivery/post_controller.rs index 861bfb8..454be2a 100644 --- a/backend/feature/post/src/adapter/delivery/post_controller.rs +++ b/backend/feature/post/src/adapter/delivery/post_controller.rs @@ -2,11 +2,14 @@ use std::sync::Arc; use async_trait::async_trait; -use crate::application::{ - error::post_error::PostError, - use_case::{ - get_all_post_info_use_case::GetAllPostInfoUseCase, - get_full_post_use_case::GetFullPostUseCase, +use crate::{ + adapter::delivery::post_info_query_dto::PostQueryDto, + application::{ + error::post_error::PostError, + use_case::{ + get_all_post_info_use_case::GetAllPostInfoUseCase, + get_full_post_use_case::GetFullPostUseCase, + }, }, }; @@ -16,10 +19,10 @@ use super::{post_info_response_dto::PostInfoResponseDto, post_response_dto::Post pub trait PostController: Send + Sync { async fn get_all_post_info( &self, - is_published_only: bool, + query: PostQueryDto, ) -> Result, PostError>; - async fn get_full_post(&self, id: i32) -> Result; + async fn get_post_by_id(&self, id: i32) -> Result; } pub struct PostControllerImpl { @@ -43,9 +46,12 @@ impl PostControllerImpl { impl PostController for PostControllerImpl { async fn get_all_post_info( &self, - is_published_only: bool, + query: PostQueryDto, ) -> Result, PostError> { - let result = self.get_all_post_info_use_case.execute(is_published_only).await; + let result = self + .get_all_post_info_use_case + .execute(query.is_published_only.unwrap_or(true)) + .await; result.map(|post_info_list| { let post_info_response_dto_list: Vec = post_info_list @@ -57,7 +63,7 @@ impl PostController for PostControllerImpl { }) } - async fn get_full_post(&self, id: i32) -> Result { + async fn get_post_by_id(&self, id: i32) -> Result { let result = self.get_full_post_use_case.execute(id).await; result.map(PostResponseDto::from) diff --git a/backend/feature/post/src/adapter/delivery/post_info_query_dto.rs b/backend/feature/post/src/adapter/delivery/post_info_query_dto.rs index 22e3676..5132186 100644 --- a/backend/feature/post/src/adapter/delivery/post_info_query_dto.rs +++ b/backend/feature/post/src/adapter/delivery/post_info_query_dto.rs @@ -1,6 +1,7 @@ use serde::Deserialize; +use utoipa::IntoParams; -#[derive(Deserialize)] +#[derive(Deserialize, IntoParams)] pub struct PostQueryDto { pub is_published_only: Option, } diff --git a/backend/feature/post/src/adapter/delivery/post_info_response_dto.rs b/backend/feature/post/src/adapter/delivery/post_info_response_dto.rs index da41320..dd33091 100644 --- a/backend/feature/post/src/adapter/delivery/post_info_response_dto.rs +++ b/backend/feature/post/src/adapter/delivery/post_info_response_dto.rs @@ -1,10 +1,11 @@ use serde::Serialize; +use utoipa::ToSchema; use crate::domain::entity::post_info::PostInfo; use super::label_response_dto::LabelResponseDto; -#[derive(Serialize)] +#[derive(Serialize, ToSchema)] pub struct PostInfoResponseDto { pub id: i32, pub title: String, diff --git a/backend/feature/post/src/adapter/delivery/post_response_dto.rs b/backend/feature/post/src/adapter/delivery/post_response_dto.rs index a4521c3..85c0c02 100644 --- a/backend/feature/post/src/adapter/delivery/post_response_dto.rs +++ b/backend/feature/post/src/adapter/delivery/post_response_dto.rs @@ -1,10 +1,11 @@ use serde::Serialize; +use utoipa::ToSchema; use crate::domain::entity::post::Post; use super::post_info_response_dto::PostInfoResponseDto; -#[derive(Serialize)] +#[derive(Serialize, ToSchema)] pub struct PostResponseDto { pub id: i32, pub info: PostInfoResponseDto, diff --git a/backend/feature/post/src/framework/web.rs b/backend/feature/post/src/framework/web.rs index f41f9a6..bacfac4 100644 --- a/backend/feature/post/src/framework/web.rs +++ b/backend/feature/post/src/framework/web.rs @@ -1 +1,5 @@ +pub mod post_api_doc; pub mod post_web_routes; + +mod get_all_post_info_handler; +mod get_post_by_id_handler; diff --git a/backend/feature/post/src/framework/web/get_all_post_info_handler.rs b/backend/feature/post/src/framework/web/get_all_post_info_handler.rs new file mode 100644 index 0000000..f0e34ca --- /dev/null +++ b/backend/feature/post/src/framework/web/get_all_post_info_handler.rs @@ -0,0 +1,33 @@ +use actix_web::{HttpResponse, Responder, web}; + +use crate::adapter::delivery::{ + post_controller::PostController, post_info_query_dto::PostQueryDto, + post_info_response_dto::PostInfoResponseDto, +}; + +#[utoipa::path( + get, + path = "/post/all", + tag = "post", + summary = "Get all post information", + params( + PostQueryDto + ), + responses ( + (status = 200, body = [PostInfoResponseDto]) + ) +)] +pub async fn get_all_post_info_handler( + post_controller: web::Data, + query: web::Query, +) -> impl Responder { + let result = post_controller.get_all_post_info(query.into_inner()).await; + + match result { + Ok(post_info_list) => HttpResponse::Ok().json(post_info_list), + Err(e) => { + log::error!("{e:?}"); + HttpResponse::InternalServerError().finish() + } + } +} 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 new file mode 100644 index 0000000..03576c8 --- /dev/null +++ b/backend/feature/post/src/framework/web/get_post_by_id_handler.rs @@ -0,0 +1,36 @@ +use actix_web::{HttpResponse, Responder, web}; + +use crate::{ + adapter::delivery::{post_controller::PostController, post_response_dto::PostResponseDto}, + application::error::post_error::PostError, +}; + +#[utoipa::path( + get, + path = "/post/{id}", + tag = "post", + summary = "Get post by ID", + responses ( + (status = 200, body = PostResponseDto), + (status = 404, description = "Post not found") + ) +)] +pub async fn get_post_by_id_handler( + post_controller: web::Data, + path: web::Path, +) -> impl Responder { + let id = path.into_inner(); + let result = post_controller.get_post_by_id(id).await; + + match result { + Ok(post) => HttpResponse::Ok().json(post), + Err(e) => { + if e == PostError::NotFound { + HttpResponse::NotFound().finish() + } else { + log::error!("{e:?}"); + HttpResponse::InternalServerError().finish() + } + } + } +} diff --git a/backend/feature/post/src/framework/web/post_api_doc.rs b/backend/feature/post/src/framework/web/post_api_doc.rs new file mode 100644 index 0000000..13ee1ce --- /dev/null +++ b/backend/feature/post/src/framework/web/post_api_doc.rs @@ -0,0 +1,16 @@ +use utoipa::{OpenApi, openapi}; +use crate::framework::web::{ + get_all_post_info_handler, + get_post_by_id_handler +}; + +#[derive(OpenApi)] +#[openapi(paths( + get_all_post_info_handler::get_all_post_info_handler, + get_post_by_id_handler::get_post_by_id_handler +))] +struct PostApiDoc; + +pub fn openapi() -> openapi::OpenApi { + PostApiDoc::openapi() +} diff --git a/backend/feature/post/src/framework/web/post_web_routes.rs b/backend/feature/post/src/framework/web/post_web_routes.rs index bb68943..74ea8d3 100644 --- a/backend/feature/post/src/framework/web/post_web_routes.rs +++ b/backend/feature/post/src/framework/web/post_web_routes.rs @@ -1,50 +1,14 @@ -use actix_web::{HttpResponse, Responder, web}; +use actix_web::web; -use crate::{ - adapter::delivery::{post_controller::PostController, post_info_query_dto::PostQueryDto}, - application::error::post_error::PostError, +use crate::framework::web::{ + get_all_post_info_handler::get_all_post_info_handler, + get_post_by_id_handler::get_post_by_id_handler, }; pub fn configure_post_routes(cfg: &mut web::ServiceConfig) { cfg.service( web::scope("/post") .route("/all", web::get().to(get_all_post_info_handler)) - .route("/{id}", web::get().to(get_full_post_handler)), + .route("/{id}", web::get().to(get_post_by_id_handler)), ); } - -async fn get_all_post_info_handler( - post_controller: web::Data, - query: web::Query, -) -> impl Responder { - let is_published_only = query.is_published_only.unwrap_or_else(|| true); - let result = post_controller.get_all_post_info(is_published_only).await; - - match result { - Ok(post_info_list) => HttpResponse::Ok().json(post_info_list), - Err(e) => { - log::error!("{e:?}"); - HttpResponse::InternalServerError().finish() - } - } -} - -async fn get_full_post_handler( - post_controller: web::Data, - path: web::Path, -) -> impl Responder { - let id = path.into_inner(); - let result = post_controller.get_full_post(id).await; - - match result { - Ok(post) => HttpResponse::Ok().json(post), - Err(e) => { - if e == PostError::NotFound { - HttpResponse::NotFound().finish() - } else { - log::error!("{e:?}"); - HttpResponse::InternalServerError().finish() - } - } - } -} diff --git a/backend/server/src/apidoc.rs b/backend/server/src/apidoc.rs index e844567..66ff21c 100644 --- a/backend/server/src/apidoc.rs +++ b/backend/server/src/apidoc.rs @@ -1,14 +1,19 @@ use actix_web::web; +use post::framework::web::post_api_doc; use utoipa::OpenApi; use utoipa_redoc::{Redoc, Servable}; #[derive(OpenApi)] -#[openapi(info( - title = "SquidSpirit API", - version = env!("CARGO_PKG_VERSION") -))] +#[openapi( + info( + title = "SquidSpirit API", + version = env!("CARGO_PKG_VERSION") + ) +)] pub struct ApiDoc; pub fn configure_api_doc_routes(cfg: &mut web::ServiceConfig) { - cfg.service(Redoc::with_url("/redoc", ApiDoc::openapi())); + let openapi = ApiDoc::openapi().merge_from(post_api_doc::openapi()); + + cfg.service(Redoc::with_url("/redoc", openapi)); }