BLOG-44 feat: add color and label mappers, update post-related structures and services
Some checks failed
Frontend CI / build (push) Failing after 51s

This commit is contained in:
SquidSpirit 2025-07-23 22:34:17 +08:00
parent acd2dcc3c8
commit a0e175dfff
14 changed files with 175 additions and 40 deletions

View File

@ -1,3 +1,4 @@
pub mod color_response_dto;
pub mod label_response_dto;
pub mod post_controller;
pub mod post_info_query_dto;

View File

@ -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,
}
}
}

View File

@ -1,12 +1,14 @@
use serde::Serialize;
use crate::domain::entity::label::Label;
use crate::{
adapter::delivery::color_response_dto::ColorResponseDto, domain::entity::label::Label,
};
#[derive(Serialize)]
pub struct LabelResponseDto {
pub id: i32,
pub name: String,
pub color: String,
pub color: ColorResponseDto,
}
impl From<Label> for LabelResponseDto {
@ -14,7 +16,7 @@ impl From<Label> for LabelResponseDto {
Self {
id: entity.id,
name: entity.name,
color: format!("#{:08X}", entity.color),
color: ColorResponseDto::from(entity.color),
}
}
}

View File

@ -1,2 +1,6 @@
pub mod color_mapper;
pub mod label_mapper;
pub mod post_db_service;
pub mod post_info_mapper;
pub mod post_mapper;
pub mod post_repository_impl;

View 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,
}
}
}

View 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(),
}
}
}

View File

@ -1,12 +1,15 @@
use async_trait::async_trait;
use crate::{
adapter::gateway::{post_info_mapper::PostInfoMapper, post_mapper::PostMapper},
application::error::post_error::PostError,
domain::entity::{post::Post, post_info::PostInfo},
};
#[async_trait]
pub trait PostDbService: Send + Sync {
async fn get_all_post_info(&self, is_published_only: bool) -> Result<Vec<PostInfo>, PostError>;
async fn get_full_post(&self, id: i32) -> Result<Post, PostError>;
async fn get_all_post_info(
&self,
is_published_only: bool,
) -> Result<Vec<PostInfoMapper>, PostError>;
async fn get_full_post(&self, id: i32) -> Result<PostMapper, PostError>;
}

View 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(),
}
}
}

View 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(),
}
}
}

View File

@ -22,10 +22,21 @@ impl PostRepositoryImpl {
#[async_trait]
impl PostRepository for PostRepositoryImpl {
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> {
self.post_db_service.get_full_post(id).await
self.post_db_service
.get_full_post(id)
.await
.map(|mapper| mapper.to_entity())
}
}

View File

@ -1,3 +1,4 @@
pub mod color;
pub mod label;
pub mod post_info;
pub mod post;

View File

@ -0,0 +1,6 @@
pub struct Color {
pub red: u8,
pub green: u8,
pub blue: u8,
pub alpha: u8,
}

View File

@ -1,5 +1,7 @@
use crate::domain::entity::color::Color;
pub struct Label {
pub id: i32,
pub name: String,
pub color: u32,
pub color: Color,
}

View File

@ -1,13 +1,14 @@
use std::collections::HashMap;
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use sqlx::{Pool, Postgres};
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,
domain::entity::{label::Label, post::Post, post_info::PostInfo},
};
use super::{
@ -27,7 +28,10 @@ impl PostDbServiceImpl {
#[async_trait]
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(
r#"
SELECT
@ -62,37 +66,37 @@ impl PostDbService for PostDbServiceImpl {
.await
.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 {
let post_info = post_info_map
let post_info = post_info_mappers_map
.entry(record.post_id)
.or_insert_with(|| PostInfo {
.or_insert_with(|| PostInfoMapper {
id: record.post_id,
title: record.title,
description: record.description,
preview_image_url: record.preview_image_url,
labels: Vec::new(),
published_time: record
.published_time
.map(|dt| DateTime::<Utc>::from_naive_utc_and_offset(dt, Utc)),
published_time: record.published_time,
});
if let (Some(label_id), Some(label_name), Some(label_color)) =
(record.label_id, record.label_name, record.label_color)
{
post_info.labels.push(Label {
post_info.labels.push(LabelMapper {
id: label_id,
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(
r#"
SELECT
@ -129,36 +133,38 @@ impl PostDbService for PostDbServiceImpl {
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 {
let post = post_map.entry(record.post_id).or_insert_with(|| Post {
id: record.post_id,
info: PostInfo {
let post = post_mappers_map
.entry(record.post_id)
.or_insert_with(|| PostMapper {
id: record.post_id,
title: record.title,
description: record.description,
preview_image_url: record.preview_image_url,
labels: Vec::new(),
published_time: record
.published_time
.map(|dt| DateTime::<Utc>::from_naive_utc_and_offset(dt, Utc)),
},
content: record.content,
});
info: PostInfoMapper {
id: record.post_id,
title: record.title,
description: record.description,
preview_image_url: record.preview_image_url,
labels: Vec::new(),
published_time: record.published_time,
},
content: record.content,
});
if let (Some(label_id), Some(label_name), Some(label_color)) =
(record.label_id, record.label_name, record.label_color)
{
post.info.labels.push(Label {
post.info.labels.push(LabelMapper {
id: label_id,
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 {
Some(v) => Ok(v),