BLOG-44 Post overall page #64
@ -1,3 +1,4 @@
|
|||||||
|
pub mod color_response_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;
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::domain::entity::color::Color;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct ColorResponseDto {
|
||||||
|
pub red: u8,
|
||||||
|
pub green: u8,
|
||||||
|
pub blue: u8,
|
||||||
|
pub alpha: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Color> for ColorResponseDto {
|
||||||
|
fn from(color: Color) -> Self {
|
||||||
|
Self {
|
||||||
|
red: color.red,
|
||||||
|
green: color.green,
|
||||||
|
blue: color.blue,
|
||||||
|
alpha: color.alpha,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,14 @@
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::domain::entity::label::Label;
|
use crate::{
|
||||||
|
adapter::delivery::color_response_dto::ColorResponseDto, domain::entity::label::Label,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct LabelResponseDto {
|
pub struct LabelResponseDto {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub color: String,
|
pub color: ColorResponseDto,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Label> for LabelResponseDto {
|
impl From<Label> for LabelResponseDto {
|
||||||
@ -14,7 +16,7 @@ impl From<Label> for LabelResponseDto {
|
|||||||
Self {
|
Self {
|
||||||
id: entity.id,
|
id: entity.id,
|
||||||
name: entity.name,
|
name: entity.name,
|
||||||
color: format!("#{:08X}", entity.color),
|
color: ColorResponseDto::from(entity.color),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1,6 @@
|
|||||||
|
pub mod color_mapper;
|
||||||
|
pub mod label_mapper;
|
||||||
pub mod post_db_service;
|
pub mod post_db_service;
|
||||||
|
pub mod post_info_mapper;
|
||||||
|
pub mod post_mapper;
|
||||||
pub mod post_repository_impl;
|
pub mod post_repository_impl;
|
||||||
|
16
backend/feature/post/src/adapter/gateway/color_mapper.rs
Normal file
16
backend/feature/post/src/adapter/gateway/color_mapper.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
use crate::domain::entity::color::Color;
|
||||||
|
|
||||||
|
pub struct ColorMapper {
|
||||||
|
pub value: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ColorMapper {
|
||||||
|
pub fn to_entity(&self) -> Color {
|
||||||
|
Color {
|
||||||
|
red: (self.value >> 24) as u8,
|
||||||
|
green: ((self.value >> 16) & 0xFF) as u8,
|
||||||
|
blue: ((self.value >> 8) & 0xFF) as u8,
|
||||||
|
alpha: (self.value & 0xFF) as u8,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
backend/feature/post/src/adapter/gateway/label_mapper.rs
Normal file
17
backend/feature/post/src/adapter/gateway/label_mapper.rs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
use crate::{adapter::gateway::color_mapper::ColorMapper, domain::entity::label::Label};
|
||||||
|
|
||||||
|
pub struct LabelMapper {
|
||||||
|
pub id: i32,
|
||||||
|
pub name: String,
|
||||||
|
pub color: ColorMapper,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LabelMapper {
|
||||||
|
pub fn to_entity(&self) -> Label {
|
||||||
|
Label {
|
||||||
|
id: self.id,
|
||||||
|
name: self.name.clone(),
|
||||||
|
color: self.color.to_entity(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,15 @@
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
adapter::gateway::{post_info_mapper::PostInfoMapper, post_mapper::PostMapper},
|
||||||
application::error::post_error::PostError,
|
application::error::post_error::PostError,
|
||||||
domain::entity::{post::Post, post_info::PostInfo},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait PostDbService: Send + Sync {
|
pub trait PostDbService: Send + Sync {
|
||||||
async fn get_all_post_info(&self, is_published_only: bool) -> Result<Vec<PostInfo>, PostError>;
|
async fn get_all_post_info(
|
||||||
async fn get_full_post(&self, id: i32) -> Result<Post, PostError>;
|
&self,
|
||||||
|
is_published_only: bool,
|
||||||
|
) -> Result<Vec<PostInfoMapper>, PostError>;
|
||||||
|
async fn get_full_post(&self, id: i32) -> Result<PostMapper, PostError>;
|
||||||
}
|
}
|
||||||
|
27
backend/feature/post/src/adapter/gateway/post_info_mapper.rs
Normal file
27
backend/feature/post/src/adapter/gateway/post_info_mapper.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||||
|
|
||||||
|
use crate::{adapter::gateway::label_mapper::LabelMapper, domain::entity::post_info::PostInfo};
|
||||||
|
|
||||||
|
pub struct PostInfoMapper {
|
||||||
|
pub id: i32,
|
||||||
|
pub title: String,
|
||||||
|
pub description: String,
|
||||||
|
pub preview_image_url: String,
|
||||||
|
pub published_time: Option<NaiveDateTime>,
|
||||||
|
pub labels: Vec<LabelMapper>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PostInfoMapper {
|
||||||
|
pub fn to_entity(&self) -> PostInfo {
|
||||||
|
PostInfo {
|
||||||
|
id: self.id,
|
||||||
|
title: self.title.clone(),
|
||||||
|
description: self.description.clone(),
|
||||||
|
preview_image_url: self.preview_image_url.clone(),
|
||||||
|
published_time: self
|
||||||
|
.published_time
|
||||||
|
.map(|dt| DateTime::<Utc>::from_naive_utc_and_offset(dt, Utc)),
|
||||||
|
labels: self.labels.iter().map(LabelMapper::to_entity).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
backend/feature/post/src/adapter/gateway/post_mapper.rs
Normal file
17
backend/feature/post/src/adapter/gateway/post_mapper.rs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
use crate::{adapter::gateway::post_info_mapper::PostInfoMapper, domain::entity::post::Post};
|
||||||
|
|
||||||
|
pub struct PostMapper {
|
||||||
|
pub id: i32,
|
||||||
|
pub info: PostInfoMapper,
|
||||||
|
pub content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PostMapper {
|
||||||
|
pub fn to_entity(&self) -> Post {
|
||||||
|
Post {
|
||||||
|
id: self.id,
|
||||||
|
info: self.info.to_entity(),
|
||||||
|
content: self.content.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -22,10 +22,21 @@ impl PostRepositoryImpl {
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl PostRepository for PostRepositoryImpl {
|
impl PostRepository for PostRepositoryImpl {
|
||||||
async fn get_all_post_info(&self, is_published_only: bool) -> Result<Vec<PostInfo>, PostError> {
|
async fn get_all_post_info(&self, is_published_only: bool) -> Result<Vec<PostInfo>, PostError> {
|
||||||
self.post_db_service.get_all_post_info(is_published_only).await
|
self.post_db_service
|
||||||
|
.get_all_post_info(is_published_only)
|
||||||
|
.await
|
||||||
|
.map(|mappers| {
|
||||||
|
mappers
|
||||||
|
.into_iter()
|
||||||
|
.map(|mapper| mapper.to_entity())
|
||||||
|
.collect::<Vec<PostInfo>>()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_full_post(&self, id: i32) -> Result<Post, PostError> {
|
async fn get_full_post(&self, id: i32) -> Result<Post, PostError> {
|
||||||
self.post_db_service.get_full_post(id).await
|
self.post_db_service
|
||||||
|
.get_full_post(id)
|
||||||
|
.await
|
||||||
|
.map(|mapper| mapper.to_entity())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
pub mod color;
|
||||||
pub mod label;
|
pub mod label;
|
||||||
pub mod post_info;
|
pub mod post_info;
|
||||||
pub mod post;
|
pub mod post;
|
||||||
|
6
backend/feature/post/src/domain/entity/color.rs
Normal file
6
backend/feature/post/src/domain/entity/color.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
pub struct Color {
|
||||||
|
pub red: u8,
|
||||||
|
pub green: u8,
|
||||||
|
pub blue: u8,
|
||||||
|
pub alpha: u8,
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
|
use crate::domain::entity::color::Color;
|
||||||
|
|
||||||
pub struct Label {
|
pub struct Label {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub color: u32,
|
pub color: Color,
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use chrono::{DateTime, Utc};
|
|
||||||
use sqlx::{Pool, Postgres};
|
use sqlx::{Pool, Postgres};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
adapter::gateway::post_db_service::PostDbService,
|
adapter::gateway::{
|
||||||
|
color_mapper::ColorMapper, label_mapper::LabelMapper, post_db_service::PostDbService,
|
||||||
|
post_info_mapper::PostInfoMapper, post_mapper::PostMapper,
|
||||||
|
},
|
||||||
application::error::post_error::PostError,
|
application::error::post_error::PostError,
|
||||||
domain::entity::{label::Label, post::Post, post_info::PostInfo},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@ -27,7 +28,10 @@ impl PostDbServiceImpl {
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl PostDbService for PostDbServiceImpl {
|
impl PostDbService for PostDbServiceImpl {
|
||||||
async fn get_all_post_info(&self, is_published_only: bool) -> Result<Vec<PostInfo>, PostError> {
|
async fn get_all_post_info(
|
||||||
|
&self,
|
||||||
|
is_published_only: bool,
|
||||||
|
) -> Result<Vec<PostInfoMapper>, PostError> {
|
||||||
let mut query_builder = sqlx::QueryBuilder::new(
|
let mut query_builder = sqlx::QueryBuilder::new(
|
||||||
r#"
|
r#"
|
||||||
SELECT
|
SELECT
|
||||||
@ -62,37 +66,37 @@ impl PostDbService for PostDbServiceImpl {
|
|||||||
.await
|
.await
|
||||||
.map_err(|err| PostError::DatabaseError(err.to_string()))?;
|
.map_err(|err| PostError::DatabaseError(err.to_string()))?;
|
||||||
|
|
||||||
let mut post_info_map = HashMap::<i32, PostInfo>::new();
|
let mut post_info_mappers_map = HashMap::<i32, PostInfoMapper>::new();
|
||||||
|
|
||||||
for record in records {
|
for record in records {
|
||||||
let post_info = post_info_map
|
let post_info = post_info_mappers_map
|
||||||
.entry(record.post_id)
|
.entry(record.post_id)
|
||||||
.or_insert_with(|| PostInfo {
|
.or_insert_with(|| PostInfoMapper {
|
||||||
id: record.post_id,
|
id: record.post_id,
|
||||||
title: record.title,
|
title: record.title,
|
||||||
description: record.description,
|
description: record.description,
|
||||||
preview_image_url: record.preview_image_url,
|
preview_image_url: record.preview_image_url,
|
||||||
labels: Vec::new(),
|
labels: Vec::new(),
|
||||||
published_time: record
|
published_time: record.published_time,
|
||||||
.published_time
|
|
||||||
.map(|dt| DateTime::<Utc>::from_naive_utc_and_offset(dt, Utc)),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if let (Some(label_id), Some(label_name), Some(label_color)) =
|
if let (Some(label_id), Some(label_name), Some(label_color)) =
|
||||||
(record.label_id, record.label_name, record.label_color)
|
(record.label_id, record.label_name, record.label_color)
|
||||||
{
|
{
|
||||||
post_info.labels.push(Label {
|
post_info.labels.push(LabelMapper {
|
||||||
id: label_id,
|
id: label_id,
|
||||||
name: label_name,
|
name: label_name,
|
||||||
color: label_color as u32,
|
color: ColorMapper {
|
||||||
|
value: label_color as u32,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(post_info_map.into_values().collect())
|
Ok(post_info_mappers_map.into_values().collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_full_post(&self, id: i32) -> Result<Post, PostError> {
|
async fn get_full_post(&self, id: i32) -> Result<PostMapper, PostError> {
|
||||||
let mut query_builder = sqlx::QueryBuilder::new(
|
let mut query_builder = sqlx::QueryBuilder::new(
|
||||||
r#"
|
r#"
|
||||||
SELECT
|
SELECT
|
||||||
@ -129,36 +133,38 @@ impl PostDbService for PostDbServiceImpl {
|
|||||||
return Err(PostError::NotFound);
|
return Err(PostError::NotFound);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut post_map = HashMap::<i32, Post>::new();
|
let mut post_mappers_map = HashMap::<i32, PostMapper>::new();
|
||||||
|
|
||||||
for record in records {
|
for record in records {
|
||||||
let post = post_map.entry(record.post_id).or_insert_with(|| Post {
|
let post = post_mappers_map
|
||||||
id: record.post_id,
|
.entry(record.post_id)
|
||||||
info: PostInfo {
|
.or_insert_with(|| PostMapper {
|
||||||
id: record.post_id,
|
id: record.post_id,
|
||||||
title: record.title,
|
info: PostInfoMapper {
|
||||||
description: record.description,
|
id: record.post_id,
|
||||||
preview_image_url: record.preview_image_url,
|
title: record.title,
|
||||||
labels: Vec::new(),
|
description: record.description,
|
||||||
published_time: record
|
preview_image_url: record.preview_image_url,
|
||||||
.published_time
|
labels: Vec::new(),
|
||||||
.map(|dt| DateTime::<Utc>::from_naive_utc_and_offset(dt, Utc)),
|
published_time: record.published_time,
|
||||||
},
|
},
|
||||||
content: record.content,
|
content: record.content,
|
||||||
});
|
});
|
||||||
|
|
||||||
if let (Some(label_id), Some(label_name), Some(label_color)) =
|
if let (Some(label_id), Some(label_name), Some(label_color)) =
|
||||||
(record.label_id, record.label_name, record.label_color)
|
(record.label_id, record.label_name, record.label_color)
|
||||||
{
|
{
|
||||||
post.info.labels.push(Label {
|
post.info.labels.push(LabelMapper {
|
||||||
id: label_id,
|
id: label_id,
|
||||||
name: label_name,
|
name: label_name,
|
||||||
color: label_color as u32,
|
color: ColorMapper {
|
||||||
|
value: label_color as u32,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let post = post_map.into_values().next();
|
let post = post_mappers_map.into_values().next();
|
||||||
|
|
||||||
match post {
|
match post {
|
||||||
Some(v) => Ok(v),
|
Some(v) => Ok(v),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user