BLOG-105 Implement CRUD functionality for Labels #107
1
backend/Cargo.lock
generated
1
backend/Cargo.lock
generated
@ -2232,6 +2232,7 @@ version = "0.2.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
"auth",
|
||||||
"chrono",
|
"chrono",
|
||||||
"log",
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -11,3 +11,5 @@ log.workspace = true
|
|||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
sqlx.workspace = true
|
sqlx.workspace = true
|
||||||
utoipa.workspace = true
|
utoipa.workspace = true
|
||||||
|
|
||||||
|
auth.workspace = true
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
|
pub mod color_request_dto;
|
||||||
pub mod color_response_dto;
|
pub mod color_response_dto;
|
||||||
|
pub mod create_label_request_dto;
|
||||||
pub mod label_response_dto;
|
pub mod label_response_dto;
|
||||||
pub mod post_controller;
|
pub mod post_controller;
|
||||||
pub mod post_info_query_dto;
|
pub mod post_info_query_dto;
|
||||||
pub mod post_info_response_dto;
|
pub mod post_info_response_dto;
|
||||||
pub mod post_response_dto;
|
pub mod post_response_dto;
|
||||||
|
pub mod update_label_request_dto;
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
use serde::Deserialize;
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
|
use crate::domain::entity::color::Color;
|
||||||
|
|
||||||
|
#[derive(Deserialize, ToSchema)]
|
||||||
|
pub struct ColorRequestDto {
|
||||||
|
#[schema(maximum = 255)]
|
||||||
|
pub red: u8,
|
||||||
|
|
||||||
|
#[schema(maximum = 255)]
|
||||||
|
pub green: u8,
|
||||||
|
|
||||||
|
#[schema(maximum = 255)]
|
||||||
|
pub blue: u8,
|
||||||
|
|
||||||
|
#[schema(maximum = 255)]
|
||||||
|
pub alpha: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ColorRequestDto {
|
||||||
|
pub fn into_entity(self) -> Color {
|
||||||
|
Color {
|
||||||
|
red: self.red,
|
||||||
|
green: self.green,
|
||||||
|
blue: self.blue,
|
||||||
|
alpha: self.alpha,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,9 +5,16 @@ use crate::domain::entity::color::Color;
|
|||||||
|
|
||||||
#[derive(Serialize, ToSchema)]
|
#[derive(Serialize, ToSchema)]
|
||||||
pub struct ColorResponseDto {
|
pub struct ColorResponseDto {
|
||||||
|
#[schema(maximum = 255)]
|
||||||
pub red: u8,
|
pub red: u8,
|
||||||
|
|
||||||
|
#[schema(maximum = 255)]
|
||||||
pub green: u8,
|
pub green: u8,
|
||||||
|
|
||||||
|
#[schema(maximum = 255)]
|
||||||
pub blue: u8,
|
pub blue: u8,
|
||||||
|
|
||||||
|
#[schema(maximum = 255)]
|
||||||
pub alpha: u8,
|
pub alpha: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
use serde::Deserialize;
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
|
use crate::{adapter::delivery::color_request_dto::ColorRequestDto, domain::entity::label::Label};
|
||||||
|
|
||||||
|
#[derive(Deserialize, ToSchema)]
|
||||||
|
pub struct CreateLabelRequestDto {
|
||||||
|
pub name: String,
|
||||||
|
pub color: ColorRequestDto,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CreateLabelRequestDto {
|
||||||
|
pub fn into_entity(self) -> Label {
|
||||||
|
Label {
|
||||||
|
id: -1,
|
||||||
|
name: self.name,
|
||||||
|
color: self.color.into_entity(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,17 +3,25 @@ use std::sync::Arc;
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
adapter::delivery::post_info_query_dto::PostQueryDto,
|
adapter::delivery::{
|
||||||
|
create_label_request_dto::CreateLabelRequestDto, post_info_query_dto::PostQueryDto,
|
||||||
|
update_label_request_dto::UpdateLabelRequestDto,
|
||||||
|
},
|
||||||
application::{
|
application::{
|
||||||
error::post_error::PostError,
|
error::post_error::PostError,
|
||||||
use_case::{
|
use_case::{
|
||||||
|
create_label_use_case::CreateLabelUseCase,
|
||||||
|
get_all_labels_use_case::GetAllLabelsUseCase,
|
||||||
get_all_post_info_use_case::GetAllPostInfoUseCase,
|
get_all_post_info_use_case::GetAllPostInfoUseCase,
|
||||||
get_full_post_use_case::GetFullPostUseCase,
|
get_full_post_use_case::GetFullPostUseCase, update_label_use_case::UpdateLabelUseCase,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{post_info_response_dto::PostInfoResponseDto, post_response_dto::PostResponseDto};
|
use super::{
|
||||||
|
label_response_dto::LabelResponseDto, post_info_response_dto::PostInfoResponseDto,
|
||||||
|
post_response_dto::PostResponseDto,
|
||||||
|
};
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait PostController: Send + Sync {
|
pub trait PostController: Send + Sync {
|
||||||
@ -23,21 +31,43 @@ pub trait PostController: Send + Sync {
|
|||||||
) -> Result<Vec<PostInfoResponseDto>, PostError>;
|
) -> Result<Vec<PostInfoResponseDto>, PostError>;
|
||||||
|
|
||||||
async fn get_post_by_id(&self, id: i32) -> Result<PostResponseDto, PostError>;
|
async fn get_post_by_id(&self, id: i32) -> Result<PostResponseDto, PostError>;
|
||||||
|
|
||||||
|
async fn create_label(
|
||||||
|
&self,
|
||||||
|
label: CreateLabelRequestDto,
|
||||||
|
) -> Result<LabelResponseDto, PostError>;
|
||||||
|
|
||||||
|
async fn update_label(
|
||||||
|
&self,
|
||||||
|
id: i32,
|
||||||
|
label: UpdateLabelRequestDto,
|
||||||
|
) -> Result<LabelResponseDto, PostError>;
|
||||||
|
|
||||||
|
async fn get_all_labels(&self) -> Result<Vec<LabelResponseDto>, PostError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PostControllerImpl {
|
pub struct PostControllerImpl {
|
||||||
get_all_post_info_use_case: Arc<dyn GetAllPostInfoUseCase>,
|
get_all_post_info_use_case: Arc<dyn GetAllPostInfoUseCase>,
|
||||||
get_full_post_use_case: Arc<dyn GetFullPostUseCase>,
|
get_full_post_use_case: Arc<dyn GetFullPostUseCase>,
|
||||||
|
create_label_use_case: Arc<dyn CreateLabelUseCase>,
|
||||||
|
update_label_use_case: Arc<dyn UpdateLabelUseCase>,
|
||||||
|
get_all_labels_use_case: Arc<dyn GetAllLabelsUseCase>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PostControllerImpl {
|
impl PostControllerImpl {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
get_all_post_info_use_case: Arc<dyn GetAllPostInfoUseCase>,
|
get_all_post_info_use_case: Arc<dyn GetAllPostInfoUseCase>,
|
||||||
get_full_post_use_case: Arc<dyn GetFullPostUseCase>,
|
get_full_post_use_case: Arc<dyn GetFullPostUseCase>,
|
||||||
|
create_label_use_case: Arc<dyn CreateLabelUseCase>,
|
||||||
|
update_label_use_case: Arc<dyn UpdateLabelUseCase>,
|
||||||
|
get_all_labels_use_case: Arc<dyn GetAllLabelsUseCase>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
get_all_post_info_use_case,
|
get_all_post_info_use_case,
|
||||||
get_full_post_use_case,
|
get_full_post_use_case,
|
||||||
|
create_label_use_case,
|
||||||
|
update_label_use_case,
|
||||||
|
get_all_labels_use_case,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -68,4 +98,42 @@ impl PostController for PostControllerImpl {
|
|||||||
|
|
||||||
result.map(PostResponseDto::from)
|
result.map(PostResponseDto::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn create_label(
|
||||||
|
&self,
|
||||||
|
label: CreateLabelRequestDto,
|
||||||
|
) -> Result<LabelResponseDto, PostError> {
|
||||||
|
let mut label_entity = label.into_entity();
|
||||||
|
let id = self
|
||||||
|
.create_label_use_case
|
||||||
|
.execute(label_entity.clone())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
label_entity.id = id;
|
||||||
|
Ok(LabelResponseDto::from(label_entity))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_label(
|
||||||
|
&self,
|
||||||
|
id: i32,
|
||||||
|
label: UpdateLabelRequestDto,
|
||||||
|
) -> Result<LabelResponseDto, PostError> {
|
||||||
|
let label_entity = label.into_entity(id);
|
||||||
|
self.update_label_use_case
|
||||||
|
.execute(label_entity.clone())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(LabelResponseDto::from(label_entity))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_all_labels(&self) -> Result<Vec<LabelResponseDto>, PostError> {
|
||||||
|
let result = self.get_all_labels_use_case.execute().await;
|
||||||
|
|
||||||
|
result.map(|labels| {
|
||||||
|
labels
|
||||||
|
.into_iter()
|
||||||
|
.map(|label| LabelResponseDto::from(label))
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
use serde::Deserialize;
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
|
use crate::{adapter::delivery::color_request_dto::ColorRequestDto, domain::entity::label::Label};
|
||||||
|
|
||||||
|
#[derive(Deserialize, ToSchema)]
|
||||||
|
pub struct UpdateLabelRequestDto {
|
||||||
|
pub name: String,
|
||||||
|
pub color: ColorRequestDto,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpdateLabelRequestDto {
|
||||||
|
pub fn into_entity(self, id: i32) -> Label {
|
||||||
|
Label {
|
||||||
|
id,
|
||||||
|
name: self.name,
|
||||||
|
color: self.color.into_entity(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
pub mod color_db_mapper;
|
pub mod color_db_mapper;
|
||||||
pub mod label_db_mapper;
|
pub mod label_db_mapper;
|
||||||
|
pub mod label_db_service;
|
||||||
|
pub mod label_repository_impl;
|
||||||
pub mod post_db_mapper;
|
pub mod post_db_mapper;
|
||||||
pub mod post_db_service;
|
pub mod post_db_service;
|
||||||
pub mod post_info_db_mapper;
|
pub mod post_info_db_mapper;
|
||||||
|
@ -14,3 +14,14 @@ impl ColorMapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Color> for ColorMapper {
|
||||||
|
fn from(color: Color) -> Self {
|
||||||
|
let value: u32 = ((color.red as u32) << 24)
|
||||||
|
| ((color.green as u32) << 16)
|
||||||
|
| ((color.blue as u32) << 8)
|
||||||
|
| (color.alpha as u32);
|
||||||
|
|
||||||
|
Self { value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -15,3 +15,13 @@ impl LabelMapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Label> for LabelMapper {
|
||||||
|
fn from(label: Label) -> Self {
|
||||||
|
Self {
|
||||||
|
id: label.id,
|
||||||
|
name: label.name,
|
||||||
|
color: ColorMapper::from(label.color),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
13
backend/feature/post/src/adapter/gateway/label_db_service.rs
Normal file
13
backend/feature/post/src/adapter/gateway/label_db_service.rs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
adapter::gateway::label_db_mapper::LabelMapper, application::error::post_error::PostError,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait LabelDbService: Send + Sync {
|
||||||
|
async fn create_label(&self, label: LabelMapper) -> Result<i32, PostError>;
|
||||||
|
async fn update_label(&self, label: LabelMapper) -> Result<(), PostError>;
|
||||||
|
async fn get_label_by_id(&self, id: i32) -> Result<LabelMapper, PostError>;
|
||||||
|
async fn get_all_labels(&self) -> Result<Vec<LabelMapper>, PostError>;
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
adapter::gateway::{label_db_mapper::LabelMapper, label_db_service::LabelDbService},
|
||||||
|
application::{error::post_error::PostError, gateway::label_repository::LabelRepository},
|
||||||
|
domain::entity::label::Label,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct LabelRepositoryImpl {
|
||||||
|
label_db_service: Arc<dyn LabelDbService>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LabelRepositoryImpl {
|
||||||
|
pub fn new(label_db_service: Arc<dyn LabelDbService>) -> Self {
|
||||||
|
Self { label_db_service }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl LabelRepository for LabelRepositoryImpl {
|
||||||
|
async fn create_label(&self, label: Label) -> Result<i32, PostError> {
|
||||||
|
self.label_db_service
|
||||||
|
.create_label(LabelMapper::from(label))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_label(&self, label: Label) -> Result<(), PostError> {
|
||||||
|
self.label_db_service
|
||||||
|
.update_label(LabelMapper::from(label))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_label_by_id(&self, id: i32) -> Result<Label, PostError> {
|
||||||
|
self.label_db_service
|
||||||
|
.get_label_by_id(id)
|
||||||
|
.await
|
||||||
|
.map(|mapper| mapper.into_entity())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_all_labels(&self) -> Result<Vec<Label>, PostError> {
|
||||||
|
self.label_db_service.get_all_labels().await.map(|mappers| {
|
||||||
|
mappers
|
||||||
|
.into_iter()
|
||||||
|
.map(|mapper| mapper.into_entity())
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -1 +1,2 @@
|
|||||||
|
pub mod label_repository;
|
||||||
pub mod post_repository;
|
pub mod post_repository;
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
use crate::{application::error::post_error::PostError, domain::entity::label::Label};
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait LabelRepository: Send + Sync {
|
||||||
|
async fn create_label(&self, label: Label) -> Result<i32, PostError>;
|
||||||
|
async fn update_label(&self, label: Label) -> Result<(), PostError>;
|
||||||
|
async fn get_label_by_id(&self, id: i32) -> Result<Label, PostError>;
|
||||||
|
async fn get_all_labels(&self) -> Result<Vec<Label>, PostError>;
|
||||||
|
}
|
@ -1,2 +1,5 @@
|
|||||||
|
pub mod create_label_use_case;
|
||||||
|
pub mod get_all_labels_use_case;
|
||||||
pub mod get_all_post_info_use_case;
|
pub mod get_all_post_info_use_case;
|
||||||
pub mod get_full_post_use_case;
|
pub mod get_full_post_use_case;
|
||||||
|
pub mod update_label_use_case;
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
application::{error::post_error::PostError, gateway::label_repository::LabelRepository},
|
||||||
|
domain::entity::label::Label,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait CreateLabelUseCase: Send + Sync {
|
||||||
|
async fn execute(&self, label: Label) -> Result<i32, PostError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CreateLabelUseCaseImpl {
|
||||||
|
label_repository: Arc<dyn LabelRepository>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CreateLabelUseCaseImpl {
|
||||||
|
pub fn new(label_repository: Arc<dyn LabelRepository>) -> Self {
|
||||||
|
Self { label_repository }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl CreateLabelUseCase for CreateLabelUseCaseImpl {
|
||||||
|
async fn execute(&self, label: Label) -> Result<i32, PostError> {
|
||||||
|
self.label_repository.create_label(label).await
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
application::{error::post_error::PostError, gateway::label_repository::LabelRepository},
|
||||||
|
domain::entity::label::Label,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait GetAllLabelsUseCase: Send + Sync {
|
||||||
|
async fn execute(&self) -> Result<Vec<Label>, PostError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GetAllLabelsUseCaseImpl {
|
||||||
|
label_repository: Arc<dyn LabelRepository>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetAllLabelsUseCaseImpl {
|
||||||
|
pub fn new(label_repository: Arc<dyn LabelRepository>) -> Self {
|
||||||
|
Self { label_repository }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl GetAllLabelsUseCase for GetAllLabelsUseCaseImpl {
|
||||||
|
async fn execute(&self) -> Result<Vec<Label>, PostError> {
|
||||||
|
self.label_repository.get_all_labels().await
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
application::{error::post_error::PostError, gateway::label_repository::LabelRepository},
|
||||||
|
domain::entity::label::Label,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait UpdateLabelUseCase: Send + Sync {
|
||||||
|
async fn execute(&self, label: Label) -> Result<(), PostError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UpdateLabelUseCaseImpl {
|
||||||
|
label_repository: Arc<dyn LabelRepository>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpdateLabelUseCaseImpl {
|
||||||
|
pub fn new(label_repository: Arc<dyn LabelRepository>) -> Self {
|
||||||
|
Self { label_repository }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl UpdateLabelUseCase for UpdateLabelUseCaseImpl {
|
||||||
|
async fn execute(&self, label: Label) -> Result<(), PostError> {
|
||||||
|
self.label_repository.get_label_by_id(label.id).await?;
|
||||||
|
self.label_repository.update_label(label).await
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
#[derive(Clone)]
|
||||||
pub struct Color {
|
pub struct Color {
|
||||||
pub red: u8,
|
pub red: u8,
|
||||||
pub green: u8,
|
pub green: u8,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use crate::domain::entity::color::Color;
|
use crate::domain::entity::color::Color;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Label {
|
pub struct Label {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
|
pub mod label_db_service_impl;
|
||||||
pub mod post_db_service_impl;
|
pub mod post_db_service_impl;
|
||||||
|
|
||||||
|
mod label_record;
|
||||||
mod post_info_with_label_record;
|
mod post_info_with_label_record;
|
||||||
mod post_with_label_record;
|
mod post_with_label_record;
|
||||||
|
102
backend/feature/post/src/framework/db/label_db_service_impl.rs
Normal file
102
backend/feature/post/src/framework/db/label_db_service_impl.rs
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
use sqlx::{Pool, Postgres};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
adapter::gateway::{label_db_mapper::LabelMapper, label_db_service::LabelDbService},
|
||||||
|
application::error::post_error::PostError,
|
||||||
|
framework::db::label_record::LabelRecord,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct LabelDbServiceImpl {
|
||||||
|
db_pool: Pool<Postgres>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LabelDbServiceImpl {
|
||||||
|
pub fn new(db_pool: Pool<Postgres>) -> Self {
|
||||||
|
Self { db_pool }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl LabelDbService for LabelDbServiceImpl {
|
||||||
|
async fn create_label(&self, label: LabelMapper) -> Result<i32, PostError> {
|
||||||
|
let id = sqlx::query_scalar!(
|
||||||
|
r#"
|
||||||
|
INSERT INTO label (name, color)
|
||||||
|
VALUES ($1, $2)
|
||||||
|
RETURNING id
|
||||||
|
"#,
|
||||||
|
label.name,
|
||||||
|
label.color.value as i64
|
||||||
|
)
|
||||||
|
.fetch_one(&self.db_pool)
|
||||||
|
.await
|
||||||
|
.map_err(|err| PostError::DatabaseError(err.to_string()))?;
|
||||||
|
|
||||||
|
Ok(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_label(&self, label: LabelMapper) -> Result<(), PostError> {
|
||||||
|
let affected_rows = sqlx::query!(
|
||||||
|
r#"
|
||||||
|
UPDATE label
|
||||||
|
SET name = $1, color = $2
|
||||||
|
WHERE id = $3 AND deleted_time IS NULL
|
||||||
|
"#,
|
||||||
|
label.name,
|
||||||
|
label.color.value as i64,
|
||||||
|
label.id
|
||||||
|
)
|
||||||
|
.execute(&self.db_pool)
|
||||||
|
.await
|
||||||
|
.map_err(|err| PostError::DatabaseError(err.to_string()))?
|
||||||
|
.rows_affected();
|
||||||
|
|
||||||
|
if affected_rows == 0 {
|
||||||
|
return Err(PostError::NotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_label_by_id(&self, id: i32) -> Result<LabelMapper, PostError> {
|
||||||
|
let record = sqlx::query_as!(
|
||||||
|
LabelRecord,
|
||||||
|
r#"
|
||||||
|
SELECT id, name, color
|
||||||
|
FROM label
|
||||||
|
WHERE id = $1 AND deleted_time IS NULL
|
||||||
|
"#,
|
||||||
|
id
|
||||||
|
)
|
||||||
|
.fetch_optional(&self.db_pool)
|
||||||
|
.await
|
||||||
|
.map_err(|err| PostError::DatabaseError(err.to_string()))?;
|
||||||
|
|
||||||
|
match record {
|
||||||
|
Some(record) => Ok(record.into_mapper()),
|
||||||
|
None => Err(PostError::NotFound),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_all_labels(&self) -> Result<Vec<LabelMapper>, PostError> {
|
||||||
|
let records = sqlx::query_as!(
|
||||||
|
LabelRecord,
|
||||||
|
r#"
|
||||||
|
SELECT id, name, color
|
||||||
|
FROM label
|
||||||
|
WHERE deleted_time IS NULL
|
||||||
|
ORDER BY id
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.fetch_all(&self.db_pool)
|
||||||
|
.await
|
||||||
|
.map_err(|err| PostError::DatabaseError(err.to_string()))?;
|
||||||
|
|
||||||
|
let mappers = records
|
||||||
|
.into_iter()
|
||||||
|
.map(|record| record.into_mapper())
|
||||||
|
.collect();
|
||||||
|
Ok(mappers)
|
||||||
|
}
|
||||||
|
}
|
19
backend/feature/post/src/framework/db/label_record.rs
Normal file
19
backend/feature/post/src/framework/db/label_record.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
use crate::adapter::gateway::{color_db_mapper::ColorMapper, label_db_mapper::LabelMapper};
|
||||||
|
|
||||||
|
pub struct LabelRecord {
|
||||||
|
pub id: i32,
|
||||||
|
pub name: String,
|
||||||
|
pub color: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LabelRecord {
|
||||||
|
pub fn into_mapper(self) -> LabelMapper {
|
||||||
|
LabelMapper {
|
||||||
|
id: self.id,
|
||||||
|
name: self.name,
|
||||||
|
color: ColorMapper {
|
||||||
|
value: self.color as u32,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,8 @@
|
|||||||
pub mod post_api_doc;
|
pub mod post_api_doc;
|
||||||
pub mod post_web_routes;
|
pub mod post_web_routes;
|
||||||
|
|
||||||
|
mod create_label_handler;
|
||||||
|
mod get_all_labels_handler;
|
||||||
mod get_all_post_info_handler;
|
mod get_all_post_info_handler;
|
||||||
mod get_post_by_id_handler;
|
mod get_post_by_id_handler;
|
||||||
|
mod update_label_handler;
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
use actix_web::{HttpResponse, Responder, web};
|
||||||
|
use auth::framework::web::auth_middleware::UserId;
|
||||||
|
|
||||||
|
use crate::adapter::delivery::{
|
||||||
|
create_label_request_dto::CreateLabelRequestDto, label_response_dto::LabelResponseDto,
|
||||||
|
post_controller::PostController,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/label",
|
||||||
|
tag = "post",
|
||||||
|
summary = "Create a new label",
|
||||||
|
responses(
|
||||||
|
(status = 201, body = LabelResponseDto),
|
||||||
|
),
|
||||||
|
security(
|
||||||
|
("oauth2" = [])
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
pub async fn create_label_handler(
|
||||||
|
post_controller: web::Data<dyn PostController>,
|
||||||
|
label_dto: web::Json<CreateLabelRequestDto>,
|
||||||
|
_: UserId,
|
||||||
|
) -> impl Responder {
|
||||||
|
let result = post_controller.create_label(label_dto.into_inner()).await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(label) => HttpResponse::Created().json(label),
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("{e:?}");
|
||||||
|
HttpResponse::InternalServerError().finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
use actix_web::{HttpResponse, Responder, web};
|
||||||
|
|
||||||
|
use crate::adapter::delivery::{
|
||||||
|
label_response_dto::LabelResponseDto, post_controller::PostController,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/label",
|
||||||
|
tag = "post",
|
||||||
|
summary = "Get all labels",
|
||||||
|
responses(
|
||||||
|
(status = 200, body = Vec<LabelResponseDto>)
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
pub async fn get_all_labels_handler(
|
||||||
|
post_controller: web::Data<dyn PostController>,
|
||||||
|
) -> impl Responder {
|
||||||
|
let result = post_controller.get_all_labels().await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(labels) => HttpResponse::Ok().json(labels),
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("{e:?}");
|
||||||
|
HttpResponse::InternalServerError().finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,16 @@
|
|||||||
use crate::framework::web::{get_all_post_info_handler, get_post_by_id_handler};
|
use crate::framework::web::{
|
||||||
|
create_label_handler, get_all_labels_handler, get_all_post_info_handler,
|
||||||
|
get_post_by_id_handler, update_label_handler,
|
||||||
|
};
|
||||||
use utoipa::{OpenApi, openapi};
|
use utoipa::{OpenApi, openapi};
|
||||||
|
|
||||||
#[derive(OpenApi)]
|
#[derive(OpenApi)]
|
||||||
#[openapi(paths(
|
#[openapi(paths(
|
||||||
get_all_post_info_handler::get_all_post_info_handler,
|
get_all_post_info_handler::get_all_post_info_handler,
|
||||||
get_post_by_id_handler::get_post_by_id_handler
|
get_post_by_id_handler::get_post_by_id_handler,
|
||||||
|
create_label_handler::create_label_handler,
|
||||||
|
update_label_handler::update_label_handler,
|
||||||
|
get_all_labels_handler::get_all_labels_handler
|
||||||
))]
|
))]
|
||||||
struct ApiDoc;
|
struct ApiDoc;
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
use actix_web::web;
|
use actix_web::web;
|
||||||
|
|
||||||
use crate::framework::web::{
|
use crate::framework::web::{
|
||||||
|
create_label_handler::create_label_handler, get_all_labels_handler::get_all_labels_handler,
|
||||||
get_all_post_info_handler::get_all_post_info_handler,
|
get_all_post_info_handler::get_all_post_info_handler,
|
||||||
get_post_by_id_handler::get_post_by_id_handler,
|
get_post_by_id_handler::get_post_by_id_handler, update_label_handler::update_label_handler,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn configure_post_routes(cfg: &mut web::ServiceConfig) {
|
pub fn configure_post_routes(cfg: &mut web::ServiceConfig) {
|
||||||
@ -11,4 +12,11 @@ pub fn configure_post_routes(cfg: &mut web::ServiceConfig) {
|
|||||||
.route("/all", web::get().to(get_all_post_info_handler))
|
.route("/all", web::get().to(get_all_post_info_handler))
|
||||||
.route("/{id}", web::get().to(get_post_by_id_handler)),
|
.route("/{id}", web::get().to(get_post_by_id_handler)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
cfg.service(
|
||||||
|
web::scope("/label")
|
||||||
|
.route("", web::get().to(get_all_labels_handler))
|
||||||
|
.route("", web::post().to(create_label_handler))
|
||||||
|
.route("/{id}", web::put().to(update_label_handler)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
use actix_web::{HttpResponse, Responder, web};
|
||||||
|
use auth::framework::web::auth_middleware::UserId;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
adapter::delivery::{
|
||||||
|
label_response_dto::LabelResponseDto, post_controller::PostController,
|
||||||
|
update_label_request_dto::UpdateLabelRequestDto,
|
||||||
|
},
|
||||||
|
application::error::post_error::PostError,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
put,
|
||||||
|
path = "/label/{id}",
|
||||||
|
tag = "post",
|
||||||
|
summary = "Update a label by ID",
|
||||||
|
responses(
|
||||||
|
(status = 200, body = LabelResponseDto),
|
||||||
|
(status = 404, description = "Label not found"),
|
||||||
|
),
|
||||||
|
security(
|
||||||
|
("oauth2" = [])
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
pub async fn update_label_handler(
|
||||||
|
post_controller: web::Data<dyn PostController>,
|
||||||
|
label_dto: web::Json<UpdateLabelRequestDto>,
|
||||||
|
path: web::Path<i32>,
|
||||||
|
_: UserId,
|
||||||
|
) -> impl Responder {
|
||||||
|
let id = path.into_inner();
|
||||||
|
let result = post_controller
|
||||||
|
.update_label(id, label_dto.into_inner())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(label) => HttpResponse::Ok().json(label),
|
||||||
|
Err(e) => match e {
|
||||||
|
PostError::NotFound => HttpResponse::NotFound().finish(),
|
||||||
|
_ => {
|
||||||
|
log::error!("{e:?}");
|
||||||
|
HttpResponse::InternalServerError().finish()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -31,13 +31,20 @@ use openidconnect::reqwest;
|
|||||||
use post::{
|
use post::{
|
||||||
adapter::{
|
adapter::{
|
||||||
delivery::post_controller::{PostController, PostControllerImpl},
|
delivery::post_controller::{PostController, PostControllerImpl},
|
||||||
gateway::post_repository_impl::PostRepositoryImpl,
|
gateway::{
|
||||||
|
label_repository_impl::LabelRepositoryImpl, post_repository_impl::PostRepositoryImpl,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
application::use_case::{
|
application::use_case::{
|
||||||
|
create_label_use_case::CreateLabelUseCaseImpl,
|
||||||
|
get_all_labels_use_case::GetAllLabelsUseCaseImpl,
|
||||||
get_all_post_info_use_case::GetAllPostInfoUseCaseImpl,
|
get_all_post_info_use_case::GetAllPostInfoUseCaseImpl,
|
||||||
get_full_post_use_case::GetFullPostUseCaseImpl,
|
get_full_post_use_case::GetFullPostUseCaseImpl,
|
||||||
|
update_label_use_case::UpdateLabelUseCaseImpl,
|
||||||
|
},
|
||||||
|
framework::db::{
|
||||||
|
label_db_service_impl::LabelDbServiceImpl, post_db_service_impl::PostDbServiceImpl,
|
||||||
},
|
},
|
||||||
framework::db::post_db_service_impl::PostDbServiceImpl,
|
|
||||||
};
|
};
|
||||||
use sqlx::{Pool, Postgres};
|
use sqlx::{Pool, Postgres};
|
||||||
|
|
||||||
@ -55,6 +62,7 @@ impl Container {
|
|||||||
http_client: reqwest::Client,
|
http_client: reqwest::Client,
|
||||||
configuration: Configuration,
|
configuration: Configuration,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
// Auth
|
||||||
let oidc_configuration = &configuration.oidc;
|
let oidc_configuration = &configuration.oidc;
|
||||||
let auth_oidc_service = Arc::new(AuthOidcServiceImpl::new(
|
let auth_oidc_service = Arc::new(AuthOidcServiceImpl::new(
|
||||||
oidc_configuration.provider_metadata.clone(),
|
oidc_configuration.provider_metadata.clone(),
|
||||||
@ -64,40 +72,61 @@ impl Container {
|
|||||||
http_client,
|
http_client,
|
||||||
));
|
));
|
||||||
let user_db_service = Arc::new(UserDbServiceImpl::new(db_pool.clone()));
|
let user_db_service = Arc::new(UserDbServiceImpl::new(db_pool.clone()));
|
||||||
|
|
||||||
let auth_repository = Arc::new(AuthRepositoryImpl::new(user_db_service, auth_oidc_service));
|
let auth_repository = Arc::new(AuthRepositoryImpl::new(user_db_service, auth_oidc_service));
|
||||||
|
|
||||||
let get_auth_url_use_case = Arc::new(LoginUseCaseImpl::new(auth_repository.clone()));
|
let get_auth_url_use_case = Arc::new(LoginUseCaseImpl::new(auth_repository.clone()));
|
||||||
let exchange_auth_code_use_case =
|
let exchange_auth_code_use_case =
|
||||||
Arc::new(ExchangeAuthCodeUseCaseImpl::new(auth_repository.clone()));
|
Arc::new(ExchangeAuthCodeUseCaseImpl::new(auth_repository.clone()));
|
||||||
let get_user_use_case = Arc::new(GetUserUseCaseImpl::new(auth_repository.clone()));
|
let get_user_use_case = Arc::new(GetUserUseCaseImpl::new(auth_repository.clone()));
|
||||||
|
|
||||||
let auth_controller = Arc::new(AuthControllerImpl::new(
|
let auth_controller = Arc::new(AuthControllerImpl::new(
|
||||||
get_auth_url_use_case,
|
get_auth_url_use_case,
|
||||||
exchange_auth_code_use_case,
|
exchange_auth_code_use_case,
|
||||||
get_user_use_case,
|
get_user_use_case,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
// Post
|
||||||
let post_db_service = Arc::new(PostDbServiceImpl::new(db_pool.clone()));
|
let post_db_service = Arc::new(PostDbServiceImpl::new(db_pool.clone()));
|
||||||
|
let label_db_service = Arc::new(LabelDbServiceImpl::new(db_pool.clone()));
|
||||||
|
|
||||||
let post_repository = Arc::new(PostRepositoryImpl::new(post_db_service.clone()));
|
let post_repository = Arc::new(PostRepositoryImpl::new(post_db_service.clone()));
|
||||||
|
let label_repository = Arc::new(LabelRepositoryImpl::new(label_db_service.clone()));
|
||||||
|
|
||||||
let get_all_post_info_use_case =
|
let get_all_post_info_use_case =
|
||||||
Arc::new(GetAllPostInfoUseCaseImpl::new(post_repository.clone()));
|
Arc::new(GetAllPostInfoUseCaseImpl::new(post_repository.clone()));
|
||||||
let get_full_post_use_case = Arc::new(GetFullPostUseCaseImpl::new(post_repository.clone()));
|
let get_full_post_use_case = Arc::new(GetFullPostUseCaseImpl::new(post_repository.clone()));
|
||||||
|
let create_label_use_case = Arc::new(CreateLabelUseCaseImpl::new(label_repository.clone()));
|
||||||
|
let update_label_use_case = Arc::new(UpdateLabelUseCaseImpl::new(label_repository.clone()));
|
||||||
|
let get_all_labels_use_case =
|
||||||
|
Arc::new(GetAllLabelsUseCaseImpl::new(label_repository.clone()));
|
||||||
|
|
||||||
let post_controller = Arc::new(PostControllerImpl::new(
|
let post_controller = Arc::new(PostControllerImpl::new(
|
||||||
get_all_post_info_use_case,
|
get_all_post_info_use_case,
|
||||||
get_full_post_use_case,
|
get_full_post_use_case,
|
||||||
|
create_label_use_case,
|
||||||
|
update_label_use_case,
|
||||||
|
get_all_labels_use_case,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
// Image
|
||||||
let image_db_service = Arc::new(ImageDbServiceImpl::new(db_pool.clone()));
|
let image_db_service = Arc::new(ImageDbServiceImpl::new(db_pool.clone()));
|
||||||
let image_storage = Arc::new(ImageStorageImpl::new(&configuration.storage.storage_path));
|
let image_storage = Arc::new(ImageStorageImpl::new(&configuration.storage.storage_path));
|
||||||
|
|
||||||
let image_repository = Arc::new(ImageRepositoryImpl::new(
|
let image_repository = Arc::new(ImageRepositoryImpl::new(
|
||||||
image_db_service.clone(),
|
image_db_service.clone(),
|
||||||
image_storage.clone(),
|
image_storage.clone(),
|
||||||
));
|
));
|
||||||
|
|
||||||
let upload_image_use_case = Arc::new(UploadImageUseCaseImpl::new(image_repository.clone()));
|
let upload_image_use_case = Arc::new(UploadImageUseCaseImpl::new(image_repository.clone()));
|
||||||
let get_image_use_case = Arc::new(GetImageUseCaseImpl::new(image_repository));
|
let get_image_use_case = Arc::new(GetImageUseCaseImpl::new(image_repository));
|
||||||
|
|
||||||
let image_controller = Arc::new(ImageControllerImpl::new(
|
let image_controller = Arc::new(ImageControllerImpl::new(
|
||||||
upload_image_use_case,
|
upload_image_use_case,
|
||||||
get_image_use_case,
|
get_image_use_case,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
// Return the container with all controllers
|
||||||
Self {
|
Self {
|
||||||
auth_controller,
|
auth_controller,
|
||||||
image_controller,
|
image_controller,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user