BLOG-142 Move label to a new feature (#143)
All checks were successful
Frontend CI / build (push) Successful in 1m35s
All checks were successful
Frontend CI / build (push) Successful in 1m35s
### Description - As the title ### Package Changes _No response_ ### Screenshots _No response_ ### Reference Resolves #142. ### Checklist - [x] A milestone is set - [x] The related issuse has been linked to this branch Reviewed-on: #143 Co-authored-by: SquidSpirit <squid@squidspirit.com> Co-committed-by: SquidSpirit <squid@squidspirit.com>
This commit is contained in:
parent
24a98f8f70
commit
a577f94acd
17
backend/Cargo.lock
generated
17
backend/Cargo.lock
generated
@ -1876,6 +1876,21 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "label"
|
||||
version = "0.3.1"
|
||||
dependencies = [
|
||||
"actix-web",
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"auth",
|
||||
"common",
|
||||
"sentry",
|
||||
"serde",
|
||||
"sqlx",
|
||||
"utoipa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "language-tags"
|
||||
version = "0.3.2"
|
||||
@ -2407,6 +2422,7 @@ dependencies = [
|
||||
"auth",
|
||||
"chrono",
|
||||
"common",
|
||||
"label",
|
||||
"regex",
|
||||
"sentry",
|
||||
"serde",
|
||||
@ -3179,6 +3195,7 @@ dependencies = [
|
||||
"env_logger",
|
||||
"hex",
|
||||
"image",
|
||||
"label",
|
||||
"openidconnect",
|
||||
"percent-encoding",
|
||||
"post",
|
||||
|
@ -4,6 +4,7 @@ members = [
|
||||
"feature/auth",
|
||||
"feature/common",
|
||||
"feature/image",
|
||||
"feature/label",
|
||||
"feature/post",
|
||||
]
|
||||
resolver = "2"
|
||||
@ -53,4 +54,5 @@ server.path = "server"
|
||||
auth.path = "feature/auth"
|
||||
common.path = "feature/common"
|
||||
image.path = "feature/image"
|
||||
label.path = "feature/label"
|
||||
post.path = "feature/post"
|
||||
|
@ -7,6 +7,12 @@ pub enum ImageError {
|
||||
Unexpected(anyhow::Error),
|
||||
}
|
||||
|
||||
impl Into<String> for ImageError {
|
||||
fn into(self) -> String {
|
||||
format!("{}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ImageError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
|
@ -15,7 +15,7 @@ use crate::{
|
||||
summary = "Get image by ID",
|
||||
responses (
|
||||
(status = 200, body = inline(ResponseBodySchema), content_type = "image/*"),
|
||||
(status = 404, description = "Image not found")
|
||||
(status = 404, description = ImageError::NotFound),
|
||||
)
|
||||
)]
|
||||
pub async fn get_image_by_id_handler(
|
||||
@ -31,12 +31,12 @@ pub async fn get_image_by_id_handler(
|
||||
.body(image_response.data),
|
||||
Err(e) => match e {
|
||||
ImageError::NotFound => HttpResponse::NotFound().finish(),
|
||||
ImageError::Unexpected(e) => {
|
||||
capture_anyhow(&e);
|
||||
ImageError::UnsupportedMimeType(_) => {
|
||||
capture_anyhow(&anyhow!(e));
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
_ => {
|
||||
capture_anyhow(&anyhow!(e));
|
||||
ImageError::Unexpected(e) => {
|
||||
capture_anyhow(&e);
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
},
|
||||
|
@ -25,7 +25,7 @@ use crate::{
|
||||
),
|
||||
responses (
|
||||
(status = 201, body = ImageInfoResponseDto),
|
||||
(status = 400, description = "Unsupported MIME type or file field not found"),
|
||||
(status = 400, description = ImageError::UnsupportedMimeType("{MIME_TYPE}".to_string())),
|
||||
),
|
||||
security(
|
||||
("oauth2" = [])
|
||||
@ -78,12 +78,12 @@ pub async fn upload_image_handler(
|
||||
ImageError::UnsupportedMimeType(mime_type) => {
|
||||
HttpResponse::BadRequest().body(format!("Unsupported MIME type: {}", mime_type))
|
||||
}
|
||||
ImageError::Unexpected(e) => {
|
||||
capture_anyhow(&e);
|
||||
ImageError::NotFound => {
|
||||
capture_anyhow(&anyhow!(e));
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
_ => {
|
||||
capture_anyhow(&anyhow!(e));
|
||||
ImageError::Unexpected(e) => {
|
||||
capture_anyhow(&e);
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
},
|
||||
|
16
backend/feature/label/Cargo.toml
Normal file
16
backend/feature/label/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "label"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
actix-web.workspace = true
|
||||
anyhow.workspace = true
|
||||
async-trait.workspace = true
|
||||
sentry.workspace = true
|
||||
serde.workspace = true
|
||||
sqlx.workspace = true
|
||||
utoipa.workspace = true
|
||||
|
||||
auth.workspace = true
|
||||
common.workspace = true
|
2
backend/feature/label/src/adapter.rs
Normal file
2
backend/feature/label/src/adapter.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod delivery;
|
||||
pub mod gateway;
|
6
backend/feature/label/src/adapter/delivery.rs
Normal file
6
backend/feature/label/src/adapter/delivery.rs
Normal file
@ -0,0 +1,6 @@
|
||||
pub mod color_request_dto;
|
||||
pub mod color_response_dto;
|
||||
pub mod create_label_request_dto;
|
||||
pub mod label_controller;
|
||||
pub mod label_response_dto;
|
||||
pub mod update_label_request_dto;
|
@ -0,0 +1,95 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::{
|
||||
adapter::delivery::{
|
||||
create_label_request_dto::CreateLabelRequestDto, label_response_dto::LabelResponseDto,
|
||||
update_label_request_dto::UpdateLabelRequestDto,
|
||||
},
|
||||
application::{
|
||||
error::label_error::LabelError,
|
||||
use_case::{
|
||||
create_label_use_case::CreateLabelUseCase,
|
||||
get_all_labels_use_case::GetAllLabelsUseCase,
|
||||
update_label_use_case::UpdateLabelUseCase,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
#[async_trait]
|
||||
pub trait LabelController: Send + Sync {
|
||||
async fn create_label(
|
||||
&self,
|
||||
label: CreateLabelRequestDto,
|
||||
) -> Result<LabelResponseDto, LabelError>;
|
||||
|
||||
async fn update_label(
|
||||
&self,
|
||||
id: i32,
|
||||
label: UpdateLabelRequestDto,
|
||||
) -> Result<LabelResponseDto, LabelError>;
|
||||
|
||||
async fn get_all_labels(&self) -> Result<Vec<LabelResponseDto>, LabelError>;
|
||||
}
|
||||
|
||||
pub struct LabelControllerImpl {
|
||||
create_label_use_case: Arc<dyn CreateLabelUseCase>,
|
||||
update_label_use_case: Arc<dyn UpdateLabelUseCase>,
|
||||
get_all_labels_use_case: Arc<dyn GetAllLabelsUseCase>,
|
||||
}
|
||||
|
||||
impl LabelControllerImpl {
|
||||
pub fn new(
|
||||
create_label_use_case: Arc<dyn CreateLabelUseCase>,
|
||||
update_label_use_case: Arc<dyn UpdateLabelUseCase>,
|
||||
get_all_labels_use_case: Arc<dyn GetAllLabelsUseCase>,
|
||||
) -> Self {
|
||||
Self {
|
||||
create_label_use_case,
|
||||
update_label_use_case,
|
||||
get_all_labels_use_case,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl LabelController for LabelControllerImpl {
|
||||
async fn create_label(
|
||||
&self,
|
||||
label: CreateLabelRequestDto,
|
||||
) -> Result<LabelResponseDto, LabelError> {
|
||||
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, LabelError> {
|
||||
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>, LabelError> {
|
||||
let result = self.get_all_labels_use_case.execute().await;
|
||||
|
||||
result.map(|labels| {
|
||||
labels
|
||||
.into_iter()
|
||||
.map(|label| LabelResponseDto::from(label))
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
}
|
4
backend/feature/label/src/adapter/gateway.rs
Normal file
4
backend/feature/label/src/adapter/gateway.rs
Normal file
@ -0,0 +1,4 @@
|
||||
pub mod color_db_mapper;
|
||||
pub mod label_db_mapper;
|
||||
pub mod label_db_service;
|
||||
pub mod label_repository_impl;
|
@ -1,13 +1,13 @@
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::{
|
||||
adapter::gateway::label_db_mapper::LabelMapper, application::error::post_error::PostError,
|
||||
adapter::gateway::label_db_mapper::LabelMapper, application::error::label_error::LabelError,
|
||||
};
|
||||
|
||||
#[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>;
|
||||
async fn create_label(&self, label: LabelMapper) -> Result<i32, LabelError>;
|
||||
async fn update_label(&self, label: LabelMapper) -> Result<(), LabelError>;
|
||||
async fn get_label_by_id(&self, id: i32) -> Result<LabelMapper, LabelError>;
|
||||
async fn get_all_labels(&self) -> Result<Vec<LabelMapper>, LabelError>;
|
||||
}
|
@ -4,7 +4,7 @@ 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},
|
||||
application::{error::label_error::LabelError, gateway::label_repository::LabelRepository},
|
||||
domain::entity::label::Label,
|
||||
};
|
||||
|
||||
@ -20,26 +20,26 @@ impl LabelRepositoryImpl {
|
||||
|
||||
#[async_trait]
|
||||
impl LabelRepository for LabelRepositoryImpl {
|
||||
async fn create_label(&self, label: Label) -> Result<i32, PostError> {
|
||||
async fn create_label(&self, label: Label) -> Result<i32, LabelError> {
|
||||
self.label_db_service
|
||||
.create_label(LabelMapper::from(label))
|
||||
.await
|
||||
}
|
||||
|
||||
async fn update_label(&self, label: Label) -> Result<(), PostError> {
|
||||
async fn update_label(&self, label: Label) -> Result<(), LabelError> {
|
||||
self.label_db_service
|
||||
.update_label(LabelMapper::from(label))
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_label_by_id(&self, id: i32) -> Result<Label, PostError> {
|
||||
async fn get_label_by_id(&self, id: i32) -> Result<Label, LabelError> {
|
||||
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> {
|
||||
async fn get_all_labels(&self) -> Result<Vec<Label>, LabelError> {
|
||||
self.label_db_service.get_all_labels().await.map(|mappers| {
|
||||
mappers
|
||||
.into_iter()
|
3
backend/feature/label/src/application.rs
Normal file
3
backend/feature/label/src/application.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod error;
|
||||
pub mod gateway;
|
||||
pub mod use_case;
|
1
backend/feature/label/src/application/error.rs
Normal file
1
backend/feature/label/src/application/error.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod label_error;
|
26
backend/feature/label/src/application/error/label_error.rs
Normal file
26
backend/feature/label/src/application/error/label_error.rs
Normal file
@ -0,0 +1,26 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum LabelError {
|
||||
NotFound,
|
||||
Unauthorized,
|
||||
DuplicatedLabelName,
|
||||
Unexpected(anyhow::Error),
|
||||
}
|
||||
|
||||
impl Into<String> for LabelError {
|
||||
fn into(self) -> String {
|
||||
format!("{}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for LabelError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
LabelError::NotFound => write!(f, "Label not found"),
|
||||
LabelError::Unauthorized => write!(f, "Unauthorized access"),
|
||||
LabelError::DuplicatedLabelName => write!(f, "Label name already exists"),
|
||||
LabelError::Unexpected(e) => write!(f, "Unexpected error: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
1
backend/feature/label/src/application/gateway.rs
Normal file
1
backend/feature/label/src/application/gateway.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod label_repository;
|
@ -0,0 +1,11 @@
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::{application::error::label_error::LabelError, domain::entity::label::Label};
|
||||
|
||||
#[async_trait]
|
||||
pub trait LabelRepository: Send + Sync {
|
||||
async fn create_label(&self, label: Label) -> Result<i32, LabelError>;
|
||||
async fn update_label(&self, label: Label) -> Result<(), LabelError>;
|
||||
async fn get_label_by_id(&self, id: i32) -> Result<Label, LabelError>;
|
||||
async fn get_all_labels(&self) -> Result<Vec<Label>, LabelError>;
|
||||
}
|
3
backend/feature/label/src/application/use_case.rs
Normal file
3
backend/feature/label/src/application/use_case.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod create_label_use_case;
|
||||
pub mod get_all_labels_use_case;
|
||||
pub mod update_label_use_case;
|
@ -3,13 +3,13 @@ use std::sync::Arc;
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::{
|
||||
application::{error::post_error::PostError, gateway::label_repository::LabelRepository},
|
||||
application::{error::label_error::LabelError, gateway::label_repository::LabelRepository},
|
||||
domain::entity::label::Label,
|
||||
};
|
||||
|
||||
#[async_trait]
|
||||
pub trait CreateLabelUseCase: Send + Sync {
|
||||
async fn execute(&self, label: Label) -> Result<i32, PostError>;
|
||||
async fn execute(&self, label: Label) -> Result<i32, LabelError>;
|
||||
}
|
||||
|
||||
pub struct CreateLabelUseCaseImpl {
|
||||
@ -24,7 +24,7 @@ impl CreateLabelUseCaseImpl {
|
||||
|
||||
#[async_trait]
|
||||
impl CreateLabelUseCase for CreateLabelUseCaseImpl {
|
||||
async fn execute(&self, label: Label) -> Result<i32, PostError> {
|
||||
async fn execute(&self, label: Label) -> Result<i32, LabelError> {
|
||||
self.label_repository.create_label(label).await
|
||||
}
|
||||
}
|
@ -3,13 +3,13 @@ use std::sync::Arc;
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::{
|
||||
application::{error::post_error::PostError, gateway::label_repository::LabelRepository},
|
||||
application::{error::label_error::LabelError, gateway::label_repository::LabelRepository},
|
||||
domain::entity::label::Label,
|
||||
};
|
||||
|
||||
#[async_trait]
|
||||
pub trait GetAllLabelsUseCase: Send + Sync {
|
||||
async fn execute(&self) -> Result<Vec<Label>, PostError>;
|
||||
async fn execute(&self) -> Result<Vec<Label>, LabelError>;
|
||||
}
|
||||
|
||||
pub struct GetAllLabelsUseCaseImpl {
|
||||
@ -24,7 +24,7 @@ impl GetAllLabelsUseCaseImpl {
|
||||
|
||||
#[async_trait]
|
||||
impl GetAllLabelsUseCase for GetAllLabelsUseCaseImpl {
|
||||
async fn execute(&self) -> Result<Vec<Label>, PostError> {
|
||||
async fn execute(&self) -> Result<Vec<Label>, LabelError> {
|
||||
self.label_repository.get_all_labels().await
|
||||
}
|
||||
}
|
@ -3,13 +3,13 @@ use std::sync::Arc;
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::{
|
||||
application::{error::post_error::PostError, gateway::label_repository::LabelRepository},
|
||||
application::{error::label_error::LabelError, gateway::label_repository::LabelRepository},
|
||||
domain::entity::label::Label,
|
||||
};
|
||||
|
||||
#[async_trait]
|
||||
pub trait UpdateLabelUseCase: Send + Sync {
|
||||
async fn execute(&self, label: Label) -> Result<(), PostError>;
|
||||
async fn execute(&self, label: Label) -> Result<(), LabelError>;
|
||||
}
|
||||
|
||||
pub struct UpdateLabelUseCaseImpl {
|
||||
@ -24,7 +24,7 @@ impl UpdateLabelUseCaseImpl {
|
||||
|
||||
#[async_trait]
|
||||
impl UpdateLabelUseCase for UpdateLabelUseCaseImpl {
|
||||
async fn execute(&self, label: Label) -> Result<(), PostError> {
|
||||
async fn execute(&self, label: Label) -> Result<(), LabelError> {
|
||||
self.label_repository.update_label(label).await
|
||||
}
|
||||
}
|
1
backend/feature/label/src/domain.rs
Normal file
1
backend/feature/label/src/domain.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod entity;
|
2
backend/feature/label/src/domain/entity.rs
Normal file
2
backend/feature/label/src/domain/entity.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod color;
|
||||
pub mod label;
|
2
backend/feature/label/src/framework.rs
Normal file
2
backend/feature/label/src/framework.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod db;
|
||||
pub mod web;
|
3
backend/feature/label/src/framework/db.rs
Normal file
3
backend/feature/label/src/framework/db.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod label_db_service_impl;
|
||||
|
||||
mod label_record;
|
@ -4,7 +4,7 @@ use sqlx::{Pool, Postgres};
|
||||
|
||||
use crate::{
|
||||
adapter::gateway::{label_db_mapper::LabelMapper, label_db_service::LabelDbService},
|
||||
application::error::post_error::PostError,
|
||||
application::error::label_error::LabelError,
|
||||
framework::db::label_record::LabelRecord,
|
||||
};
|
||||
|
||||
@ -20,7 +20,7 @@ impl LabelDbServiceImpl {
|
||||
|
||||
#[async_trait]
|
||||
impl LabelDbService for LabelDbServiceImpl {
|
||||
async fn create_label(&self, label: LabelMapper) -> Result<i32, PostError> {
|
||||
async fn create_label(&self, label: LabelMapper) -> Result<i32, LabelError> {
|
||||
let id = sqlx::query_scalar!(
|
||||
r#"
|
||||
INSERT INTO label (name, color)
|
||||
@ -35,16 +35,16 @@ impl LabelDbService for LabelDbServiceImpl {
|
||||
.map_err(|e| {
|
||||
if let sqlx::Error::Database(db_err) = &e {
|
||||
if db_err.constraint() == Some("idx_label_name") {
|
||||
return PostError::DuplicatedLabelName;
|
||||
return LabelError::DuplicatedLabelName;
|
||||
}
|
||||
}
|
||||
PostError::Unexpected(DatabaseError(e).into())
|
||||
LabelError::Unexpected(DatabaseError(e).into())
|
||||
})?;
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
async fn update_label(&self, label: LabelMapper) -> Result<(), PostError> {
|
||||
async fn update_label(&self, label: LabelMapper) -> Result<(), LabelError> {
|
||||
let affected_rows = sqlx::query!(
|
||||
r#"
|
||||
UPDATE label
|
||||
@ -60,21 +60,21 @@ impl LabelDbService for LabelDbServiceImpl {
|
||||
.map_err(|e| {
|
||||
if let sqlx::Error::Database(db_err) = &e {
|
||||
if db_err.constraint() == Some("idx_label_name") {
|
||||
return PostError::DuplicatedLabelName;
|
||||
return LabelError::DuplicatedLabelName;
|
||||
}
|
||||
}
|
||||
PostError::Unexpected(DatabaseError(e).into())
|
||||
LabelError::Unexpected(DatabaseError(e).into())
|
||||
})?
|
||||
.rows_affected();
|
||||
|
||||
if affected_rows == 0 {
|
||||
return Err(PostError::NotFound);
|
||||
return Err(LabelError::NotFound);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_label_by_id(&self, id: i32) -> Result<LabelMapper, PostError> {
|
||||
async fn get_label_by_id(&self, id: i32) -> Result<LabelMapper, LabelError> {
|
||||
let record = sqlx::query_as!(
|
||||
LabelRecord,
|
||||
r#"
|
||||
@ -86,15 +86,15 @@ impl LabelDbService for LabelDbServiceImpl {
|
||||
)
|
||||
.fetch_optional(&self.db_pool)
|
||||
.await
|
||||
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
|
||||
.map_err(|e| LabelError::Unexpected(DatabaseError(e).into()))?;
|
||||
|
||||
match record {
|
||||
Some(record) => Ok(record.into_mapper()),
|
||||
None => Err(PostError::NotFound),
|
||||
None => Err(LabelError::NotFound),
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_all_labels(&self) -> Result<Vec<LabelMapper>, PostError> {
|
||||
async fn get_all_labels(&self) -> Result<Vec<LabelMapper>, LabelError> {
|
||||
let records = sqlx::query_as!(
|
||||
LabelRecord,
|
||||
r#"
|
||||
@ -106,7 +106,7 @@ impl LabelDbService for LabelDbServiceImpl {
|
||||
)
|
||||
.fetch_all(&self.db_pool)
|
||||
.await
|
||||
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
|
||||
.map_err(|e| LabelError::Unexpected(DatabaseError(e).into()))?;
|
||||
|
||||
let mappers = records
|
||||
.into_iter()
|
5
backend/feature/label/src/framework/web.rs
Normal file
5
backend/feature/label/src/framework/web.rs
Normal file
@ -0,0 +1,5 @@
|
||||
pub mod create_label_handler;
|
||||
pub mod get_all_labels_handler;
|
||||
pub mod label_api_doc;
|
||||
pub mod label_web_routes;
|
||||
pub mod update_label_handler;
|
@ -5,43 +5,43 @@ use sentry::integrations::anyhow::capture_anyhow;
|
||||
|
||||
use crate::{
|
||||
adapter::delivery::{
|
||||
create_label_request_dto::CreateLabelRequestDto, label_response_dto::LabelResponseDto,
|
||||
post_controller::PostController,
|
||||
create_label_request_dto::CreateLabelRequestDto, label_controller::LabelController,
|
||||
label_response_dto::LabelResponseDto,
|
||||
},
|
||||
application::error::post_error::PostError,
|
||||
application::error::label_error::LabelError,
|
||||
};
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/label",
|
||||
tag = "post",
|
||||
tag = "label",
|
||||
summary = "Create a new label",
|
||||
responses(
|
||||
(status = 201, body = LabelResponseDto),
|
||||
(status = 401, description = LabelError::Unauthorized),
|
||||
(status = 409, description = LabelError::DuplicatedLabelName),
|
||||
),
|
||||
security(
|
||||
("oauth2" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn create_label_handler(
|
||||
post_controller: web::Data<dyn PostController>,
|
||||
label_controller: web::Data<dyn LabelController>,
|
||||
label_dto: web::Json<CreateLabelRequestDto>,
|
||||
_: UserId,
|
||||
) -> impl Responder {
|
||||
let result = post_controller.create_label(label_dto.into_inner()).await;
|
||||
let result = label_controller.create_label(label_dto.into_inner()).await;
|
||||
|
||||
match result {
|
||||
Ok(label) => HttpResponse::Created().json(label),
|
||||
Err(e) => match e {
|
||||
PostError::Unauthorized => HttpResponse::Unauthorized().finish(),
|
||||
PostError::DuplicatedLabelName => HttpResponse::Conflict().finish(),
|
||||
PostError::NotFound
|
||||
| PostError::InvalidSemanticId
|
||||
| PostError::DuplicatedSemanticId => {
|
||||
LabelError::Unauthorized => HttpResponse::Unauthorized().finish(),
|
||||
LabelError::DuplicatedLabelName => HttpResponse::Conflict().finish(),
|
||||
LabelError::NotFound => {
|
||||
capture_anyhow(&anyhow!(e));
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
PostError::Unexpected(e) => {
|
||||
LabelError::Unexpected(e) => {
|
||||
capture_anyhow(&e);
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
@ -3,36 +3,32 @@ use anyhow::anyhow;
|
||||
use sentry::integrations::anyhow::capture_anyhow;
|
||||
|
||||
use crate::{
|
||||
adapter::delivery::{label_response_dto::LabelResponseDto, post_controller::PostController},
|
||||
application::error::post_error::PostError,
|
||||
adapter::delivery::{label_controller::LabelController, label_response_dto::LabelResponseDto},
|
||||
application::error::label_error::LabelError,
|
||||
};
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/label",
|
||||
tag = "post",
|
||||
tag = "label",
|
||||
summary = "Get all labels",
|
||||
responses(
|
||||
(status = 200, body = Vec<LabelResponseDto>)
|
||||
)
|
||||
)]
|
||||
pub async fn get_all_labels_handler(
|
||||
post_controller: web::Data<dyn PostController>,
|
||||
label_controller: web::Data<dyn LabelController>,
|
||||
) -> impl Responder {
|
||||
let result = post_controller.get_all_labels().await;
|
||||
let result = label_controller.get_all_labels().await;
|
||||
|
||||
match result {
|
||||
Ok(labels) => HttpResponse::Ok().json(labels),
|
||||
Err(e) => match e {
|
||||
PostError::NotFound
|
||||
| PostError::Unauthorized
|
||||
| PostError::InvalidSemanticId
|
||||
| PostError::DuplicatedSemanticId
|
||||
| PostError::DuplicatedLabelName => {
|
||||
LabelError::NotFound | LabelError::Unauthorized | LabelError::DuplicatedLabelName => {
|
||||
capture_anyhow(&anyhow!(e));
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
PostError::Unexpected(e) => {
|
||||
LabelError::Unexpected(e) => {
|
||||
capture_anyhow(&e);
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
14
backend/feature/label/src/framework/web/label_api_doc.rs
Normal file
14
backend/feature/label/src/framework/web/label_api_doc.rs
Normal file
@ -0,0 +1,14 @@
|
||||
use crate::framework::web::{create_label_handler, get_all_labels_handler, update_label_handler};
|
||||
use utoipa::{OpenApi, openapi};
|
||||
|
||||
#[derive(OpenApi)]
|
||||
#[openapi(paths(
|
||||
create_label_handler::create_label_handler,
|
||||
update_label_handler::update_label_handler,
|
||||
get_all_labels_handler::get_all_labels_handler
|
||||
))]
|
||||
struct ApiDoc;
|
||||
|
||||
pub fn openapi() -> openapi::OpenApi {
|
||||
ApiDoc::openapi()
|
||||
}
|
15
backend/feature/label/src/framework/web/label_web_routes.rs
Normal file
15
backend/feature/label/src/framework/web/label_web_routes.rs
Normal file
@ -0,0 +1,15 @@
|
||||
use actix_web::web;
|
||||
|
||||
use crate::framework::web::{
|
||||
create_label_handler::create_label_handler, get_all_labels_handler::get_all_labels_handler,
|
||||
update_label_handler::update_label_handler,
|
||||
};
|
||||
|
||||
pub fn configure_label_routes(cfg: &mut web::ServiceConfig) {
|
||||
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)),
|
||||
);
|
||||
}
|
@ -1,51 +1,48 @@
|
||||
use actix_web::{HttpResponse, Responder, web};
|
||||
use anyhow::anyhow;
|
||||
use auth::framework::web::auth_middleware::UserId;
|
||||
use sentry::integrations::anyhow::capture_anyhow;
|
||||
|
||||
use crate::{
|
||||
adapter::delivery::{
|
||||
label_response_dto::LabelResponseDto, post_controller::PostController,
|
||||
label_controller::LabelController, label_response_dto::LabelResponseDto,
|
||||
update_label_request_dto::UpdateLabelRequestDto,
|
||||
},
|
||||
application::error::post_error::PostError,
|
||||
application::error::label_error::LabelError,
|
||||
};
|
||||
|
||||
#[utoipa::path(
|
||||
put,
|
||||
path = "/label/{id}",
|
||||
tag = "post",
|
||||
tag = "label",
|
||||
summary = "Update a label by ID",
|
||||
responses(
|
||||
(status = 200, body = LabelResponseDto),
|
||||
(status = 404, description = "Label not found"),
|
||||
(status = 401, description = LabelError::Unauthorized),
|
||||
(status = 404, description = LabelError::NotFound),
|
||||
(status = 409, description = LabelError::DuplicatedLabelName),
|
||||
),
|
||||
security(
|
||||
("oauth2" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn update_label_handler(
|
||||
post_controller: web::Data<dyn PostController>,
|
||||
label_controller: web::Data<dyn LabelController>,
|
||||
label_dto: web::Json<UpdateLabelRequestDto>,
|
||||
path: web::Path<i32>,
|
||||
_: UserId,
|
||||
) -> impl Responder {
|
||||
let id = path.into_inner();
|
||||
let result = post_controller
|
||||
let result = label_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(),
|
||||
PostError::Unauthorized => HttpResponse::Unauthorized().finish(),
|
||||
PostError::DuplicatedLabelName => HttpResponse::Conflict().finish(),
|
||||
PostError::InvalidSemanticId | PostError::DuplicatedSemanticId => {
|
||||
capture_anyhow(&anyhow!(e));
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
PostError::Unexpected(e) => {
|
||||
LabelError::NotFound => HttpResponse::NotFound().finish(),
|
||||
LabelError::Unauthorized => HttpResponse::Unauthorized().finish(),
|
||||
LabelError::DuplicatedLabelName => HttpResponse::Conflict().finish(),
|
||||
LabelError::Unexpected(e) => {
|
||||
capture_anyhow(&e);
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
4
backend/feature/label/src/lib.rs
Normal file
4
backend/feature/label/src/lib.rs
Normal file
@ -0,0 +1,4 @@
|
||||
pub mod adapter;
|
||||
pub mod application;
|
||||
pub mod domain;
|
||||
pub mod framework;
|
@ -16,3 +16,4 @@ utoipa.workspace = true
|
||||
|
||||
auth.workspace = true
|
||||
common.workspace = true
|
||||
label.workspace = true
|
||||
|
@ -1,11 +1,6 @@
|
||||
pub mod color_request_dto;
|
||||
pub mod color_response_dto;
|
||||
pub mod create_label_request_dto;
|
||||
pub mod create_post_request_dto;
|
||||
pub mod label_response_dto;
|
||||
pub mod post_controller;
|
||||
pub mod post_info_query_dto;
|
||||
pub mod post_info_response_dto;
|
||||
pub mod post_response_dto;
|
||||
pub mod update_label_request_dto;
|
||||
pub mod update_post_request_dto;
|
||||
|
@ -4,28 +4,18 @@ use async_trait::async_trait;
|
||||
|
||||
use crate::{
|
||||
adapter::delivery::{
|
||||
create_label_request_dto::CreateLabelRequestDto,
|
||||
create_post_request_dto::CreatePostRequestDto, post_info_query_dto::PostQueryDto,
|
||||
update_label_request_dto::UpdateLabelRequestDto,
|
||||
update_post_request_dto::UpdatePostRequestDto,
|
||||
},
|
||||
application::{
|
||||
error::post_error::PostError,
|
||||
use_case::{
|
||||
create_label_use_case::CreateLabelUseCase, create_post_use_case::CreatePostUseCase,
|
||||
get_all_labels_use_case::GetAllLabelsUseCase,
|
||||
get_all_post_info_use_case::GetAllPostInfoUseCase,
|
||||
get_post_by_id_use_case::GetPostByIdUseCase,
|
||||
get_post_by_semantic_id_use_case::GetPostBySemanticIdUseCase,
|
||||
update_label_use_case::UpdateLabelUseCase, update_post_use_case::UpdatePostUseCase,
|
||||
create_post_use_case::CreatePostUseCase, get_all_post_info_use_case::GetAllPostInfoUseCase, get_post_by_id_use_case::GetPostByIdUseCase, get_post_by_semantic_id_use_case::GetPostBySemanticIdUseCase, update_post_use_case::UpdatePostUseCase
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
use super::{
|
||||
label_response_dto::LabelResponseDto, post_info_response_dto::PostInfoResponseDto,
|
||||
post_response_dto::PostResponseDto,
|
||||
};
|
||||
use super::{post_info_response_dto::PostInfoResponseDto, post_response_dto::PostResponseDto};
|
||||
|
||||
#[async_trait]
|
||||
pub trait PostController: Send + Sync {
|
||||
@ -53,19 +43,6 @@ pub trait PostController: Send + Sync {
|
||||
post: UpdatePostRequestDto,
|
||||
user_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 {
|
||||
@ -74,9 +51,6 @@ pub struct PostControllerImpl {
|
||||
get_post_by_semantic_id_use_case: Arc<dyn GetPostBySemanticIdUseCase>,
|
||||
create_post_use_case: Arc<dyn CreatePostUseCase>,
|
||||
update_post_use_case: Arc<dyn UpdatePostUseCase>,
|
||||
create_label_use_case: Arc<dyn CreateLabelUseCase>,
|
||||
update_label_use_case: Arc<dyn UpdateLabelUseCase>,
|
||||
get_all_labels_use_case: Arc<dyn GetAllLabelsUseCase>,
|
||||
}
|
||||
|
||||
impl PostControllerImpl {
|
||||
@ -86,9 +60,6 @@ impl PostControllerImpl {
|
||||
get_post_by_semantic_id_use_case: Arc<dyn GetPostBySemanticIdUseCase>,
|
||||
create_post_use_case: Arc<dyn CreatePostUseCase>,
|
||||
update_post_use_case: Arc<dyn UpdatePostUseCase>,
|
||||
create_label_use_case: Arc<dyn CreateLabelUseCase>,
|
||||
update_label_use_case: Arc<dyn UpdateLabelUseCase>,
|
||||
get_all_labels_use_case: Arc<dyn GetAllLabelsUseCase>,
|
||||
) -> Self {
|
||||
Self {
|
||||
get_all_post_info_use_case,
|
||||
@ -96,9 +67,6 @@ impl PostControllerImpl {
|
||||
get_post_by_semantic_id_use_case,
|
||||
create_post_use_case,
|
||||
update_post_use_case,
|
||||
create_label_use_case,
|
||||
update_label_use_case,
|
||||
get_all_labels_use_case,
|
||||
}
|
||||
}
|
||||
|
||||
@ -161,44 +129,6 @@ impl PostController for PostControllerImpl {
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
})
|
||||
}
|
||||
|
||||
async fn create_post(
|
||||
&self,
|
||||
post: CreatePostRequestDto,
|
||||
|
@ -1,10 +1,9 @@
|
||||
use label::adapter::delivery::label_response_dto::LabelResponseDto;
|
||||
use serde::Serialize;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::domain::entity::post_info::PostInfo;
|
||||
|
||||
use super::label_response_dto::LabelResponseDto;
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct PostInfoResponseDto {
|
||||
pub id: i32,
|
||||
|
@ -1,7 +1,3 @@
|
||||
pub mod color_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_service;
|
||||
pub mod post_info_db_mapper;
|
||||
|
@ -1,6 +1,7 @@
|
||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||
use label::adapter::gateway::label_db_mapper::LabelMapper;
|
||||
|
||||
use crate::{adapter::gateway::label_db_mapper::LabelMapper, domain::entity::post_info::PostInfo};
|
||||
use crate::domain::entity::post_info::PostInfo;
|
||||
|
||||
pub struct PostInfoMapper {
|
||||
pub id: i32,
|
||||
|
@ -6,21 +6,25 @@ pub enum PostError {
|
||||
Unauthorized,
|
||||
InvalidSemanticId,
|
||||
DuplicatedSemanticId,
|
||||
DuplicatedLabelName,
|
||||
Unexpected(anyhow::Error),
|
||||
}
|
||||
|
||||
impl Into<String> for PostError {
|
||||
fn into(self) -> String {
|
||||
format!("{}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for PostError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
PostError::NotFound => write!(f, "Post not found"),
|
||||
PostError::Unauthorized => write!(f, "Unauthorized access to post"),
|
||||
PostError::Unauthorized => write!(f, "Unauthorized access"),
|
||||
PostError::InvalidSemanticId => write!(
|
||||
f,
|
||||
"Semantic ID shouldn't be numeric and must conform to `^[0-9a-zA-Z_\\-]+$`"
|
||||
"Semantic ID shouldn't be numeric and must conform to `^[0-9a-zA-Z_-]+$`"
|
||||
),
|
||||
PostError::DuplicatedSemanticId => write!(f, "Semantic ID already exists"),
|
||||
PostError::DuplicatedLabelName => write!(f, "Label name already exists"),
|
||||
PostError::Unexpected(e) => write!(f, "Unexpected error: {}", e),
|
||||
}
|
||||
}
|
||||
|
@ -1,2 +1 @@
|
||||
pub mod label_repository;
|
||||
pub mod post_repository;
|
||||
|
@ -1,11 +0,0 @@
|
||||
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,8 +1,5 @@
|
||||
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_post_by_id_use_case;
|
||||
pub mod get_post_by_semantic_id_use_case;
|
||||
pub mod update_label_use_case;
|
||||
pub mod update_post_use_case;
|
||||
|
@ -1,4 +1,2 @@
|
||||
pub mod color;
|
||||
pub mod label;
|
||||
pub mod post_info;
|
||||
pub mod post;
|
||||
|
@ -1,10 +1,9 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use label::domain::entity::label::Label;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::application::error::post_error::PostError;
|
||||
|
||||
use super::label::Label;
|
||||
|
||||
pub struct PostInfo {
|
||||
pub id: i32,
|
||||
pub semantic_id: String,
|
||||
@ -21,7 +20,7 @@ impl PostInfo {
|
||||
return Err(PostError::InvalidSemanticId);
|
||||
}
|
||||
|
||||
let re = Regex::new(r"^[0-9a-zA-Z_\-]+$").unwrap();
|
||||
let re = Regex::new(r"^[0-9a-zA-Z_-]+$").unwrap();
|
||||
if !re.is_match(&self.semantic_id) {
|
||||
return Err(PostError::InvalidSemanticId);
|
||||
}
|
||||
|
@ -1,6 +1,4 @@
|
||||
pub mod label_db_service_impl;
|
||||
pub mod post_db_service_impl;
|
||||
|
||||
mod label_record;
|
||||
mod post_info_with_label_record;
|
||||
mod post_with_label_record;
|
||||
|
@ -2,12 +2,13 @@ use std::collections::HashMap;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use common::framework::error::DatabaseError;
|
||||
use label::adapter::gateway::{color_db_mapper::ColorMapper, label_db_mapper::LabelMapper};
|
||||
use sqlx::{Pool, Postgres};
|
||||
|
||||
use crate::{
|
||||
adapter::gateway::{
|
||||
color_db_mapper::ColorMapper, label_db_mapper::LabelMapper, post_db_mapper::PostMapper,
|
||||
post_db_service::PostDbService, post_info_db_mapper::PostInfoMapper,
|
||||
post_db_mapper::PostMapper, post_db_service::PostDbService,
|
||||
post_info_db_mapper::PostInfoMapper,
|
||||
},
|
||||
application::error::post_error::PostError,
|
||||
};
|
||||
|
@ -1,10 +1,7 @@
|
||||
pub mod post_api_doc;
|
||||
pub mod post_web_routes;
|
||||
|
||||
mod create_label_handler;
|
||||
mod create_post_handler;
|
||||
mod get_all_labels_handler;
|
||||
mod get_all_post_info_handler;
|
||||
mod get_post_by_id_handler;
|
||||
mod update_label_handler;
|
||||
mod update_post_handler;
|
||||
|
@ -18,6 +18,9 @@ use crate::{
|
||||
summary = "Create a new post",
|
||||
responses(
|
||||
(status = 201, body = PostResponseDto),
|
||||
(status = 400, description = PostError::InvalidSemanticId),
|
||||
(status = 401, description = PostError::Unauthorized),
|
||||
(status = 409, description = PostError::DuplicatedSemanticId),
|
||||
),
|
||||
security(
|
||||
("oauth2" = [])
|
||||
@ -38,7 +41,7 @@ pub async fn create_post_handler(
|
||||
PostError::Unauthorized => HttpResponse::Unauthorized().finish(),
|
||||
PostError::InvalidSemanticId => HttpResponse::BadRequest().finish(),
|
||||
PostError::DuplicatedSemanticId => HttpResponse::Conflict().finish(),
|
||||
PostError::NotFound | PostError::DuplicatedLabelName => {
|
||||
PostError::NotFound => {
|
||||
capture_anyhow(&anyhow!(e));
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
|
@ -39,8 +39,7 @@ pub async fn get_all_post_info_handler(
|
||||
PostError::NotFound
|
||||
| PostError::Unauthorized
|
||||
| PostError::InvalidSemanticId
|
||||
| PostError::DuplicatedSemanticId
|
||||
| PostError::DuplicatedLabelName => {
|
||||
| PostError::DuplicatedSemanticId => {
|
||||
capture_anyhow(&anyhow!(e));
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
|
@ -16,7 +16,8 @@ use crate::{
|
||||
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")
|
||||
(status = 401, description = PostError::Unauthorized),
|
||||
(status = 404, description = PostError::NotFound),
|
||||
)
|
||||
)]
|
||||
pub async fn get_post_by_id_handler(
|
||||
@ -34,9 +35,7 @@ pub async fn get_post_by_id_handler(
|
||||
Err(e) => match e {
|
||||
PostError::NotFound => HttpResponse::NotFound().finish(),
|
||||
PostError::Unauthorized => HttpResponse::Unauthorized().finish(),
|
||||
PostError::InvalidSemanticId
|
||||
| PostError::DuplicatedSemanticId
|
||||
| PostError::DuplicatedLabelName => {
|
||||
PostError::InvalidSemanticId | PostError::DuplicatedSemanticId => {
|
||||
capture_anyhow(&anyhow!(e));
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
use crate::framework::web::{
|
||||
create_label_handler, create_post_handler, get_all_labels_handler, get_all_post_info_handler,
|
||||
get_post_by_id_handler, update_label_handler, update_post_handler,
|
||||
create_post_handler, get_all_post_info_handler, get_post_by_id_handler, update_post_handler,
|
||||
};
|
||||
use utoipa::{OpenApi, openapi};
|
||||
|
||||
@ -10,9 +9,6 @@ use utoipa::{OpenApi, openapi};
|
||||
get_post_by_id_handler::get_post_by_id_handler,
|
||||
create_post_handler::create_post_handler,
|
||||
update_post_handler::update_post_handler,
|
||||
create_label_handler::create_label_handler,
|
||||
update_label_handler::update_label_handler,
|
||||
get_all_labels_handler::get_all_labels_handler
|
||||
))]
|
||||
struct ApiDoc;
|
||||
|
||||
|
@ -1,11 +1,8 @@
|
||||
use actix_web::web;
|
||||
|
||||
use crate::framework::web::{
|
||||
create_label_handler::create_label_handler, create_post_handler::create_post_handler,
|
||||
get_all_labels_handler::get_all_labels_handler,
|
||||
get_all_post_info_handler::get_all_post_info_handler,
|
||||
get_post_by_id_handler::get_post_by_id_handler, update_label_handler::update_label_handler,
|
||||
update_post_handler::update_post_handler,
|
||||
create_post_handler::create_post_handler, get_all_post_info_handler::get_all_post_info_handler,
|
||||
get_post_by_id_handler::get_post_by_id_handler, update_post_handler::update_post_handler,
|
||||
};
|
||||
|
||||
pub fn configure_post_routes(cfg: &mut web::ServiceConfig) {
|
||||
@ -16,11 +13,4 @@ pub fn configure_post_routes(cfg: &mut web::ServiceConfig) {
|
||||
.route("/{id}", web::get().to(get_post_by_id_handler))
|
||||
.route("/{id}", web::put().to(update_post_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)),
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
use actix_web::{HttpResponse, Responder, web};
|
||||
use anyhow::anyhow;
|
||||
use auth::framework::web::auth_middleware::UserId;
|
||||
use sentry::integrations::anyhow::capture_anyhow;
|
||||
|
||||
@ -18,7 +17,10 @@ use crate::{
|
||||
summary = "Update a post by ID",
|
||||
responses(
|
||||
(status = 200, body = PostResponseDto),
|
||||
(status = 404, description = "Post not found"),
|
||||
(status = 400, description = PostError::InvalidSemanticId),
|
||||
(status = 401, description = PostError::Unauthorized),
|
||||
(status = 404, description = PostError::NotFound),
|
||||
(status = 409, description = PostError::DuplicatedSemanticId),
|
||||
),
|
||||
security(
|
||||
("oauth2" = [])
|
||||
@ -42,10 +44,6 @@ pub async fn update_post_handler(
|
||||
PostError::Unauthorized => HttpResponse::Unauthorized().finish(),
|
||||
PostError::DuplicatedSemanticId => HttpResponse::Conflict().finish(),
|
||||
PostError::InvalidSemanticId => HttpResponse::BadRequest().finish(),
|
||||
PostError::DuplicatedLabelName => {
|
||||
capture_anyhow(&anyhow!(e));
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
PostError::Unexpected(e) => {
|
||||
capture_anyhow(&e);
|
||||
HttpResponse::InternalServerError().finish()
|
||||
|
@ -18,4 +18,5 @@ utoipa-redoc.workspace = true
|
||||
|
||||
auth.workspace = true
|
||||
image.workspace = true
|
||||
label.workspace = true
|
||||
post.workspace = true
|
||||
|
@ -1,6 +1,7 @@
|
||||
use actix_web::web;
|
||||
use auth::framework::web::auth_api_doc;
|
||||
use image::framework::web::image_api_doc;
|
||||
use label::framework::web::label_api_doc;
|
||||
use post::framework::web::post_api_doc;
|
||||
use utoipa::{
|
||||
OpenApi,
|
||||
@ -40,6 +41,7 @@ pub fn configure_api_doc_routes(cfg: &mut web::ServiceConfig) {
|
||||
let openapi = ApiDoc::openapi()
|
||||
.merge_from(auth_api_doc::openapi())
|
||||
.merge_from(image_api_doc::openapi())
|
||||
.merge_from(label_api_doc::openapi())
|
||||
.merge_from(post_api_doc::openapi());
|
||||
|
||||
cfg.service(Redoc::with_url("/redoc", openapi));
|
||||
|
@ -27,25 +27,32 @@ use image::{
|
||||
storage::image_storage_impl::ImageStorageImpl,
|
||||
},
|
||||
};
|
||||
use label::{
|
||||
adapter::{
|
||||
delivery::label_controller::{LabelController, LabelControllerImpl},
|
||||
gateway::label_repository_impl::LabelRepositoryImpl,
|
||||
},
|
||||
application::use_case::{
|
||||
create_label_use_case::CreateLabelUseCaseImpl,
|
||||
get_all_labels_use_case::GetAllLabelsUseCaseImpl,
|
||||
update_label_use_case::UpdateLabelUseCaseImpl,
|
||||
},
|
||||
framework::db::label_db_service_impl::LabelDbServiceImpl,
|
||||
};
|
||||
use openidconnect::reqwest;
|
||||
use post::{
|
||||
adapter::{
|
||||
delivery::post_controller::{PostController, PostControllerImpl},
|
||||
gateway::{
|
||||
label_repository_impl::LabelRepositoryImpl, post_repository_impl::PostRepositoryImpl,
|
||||
},
|
||||
gateway::post_repository_impl::PostRepositoryImpl,
|
||||
},
|
||||
application::use_case::{
|
||||
create_label_use_case::CreateLabelUseCaseImpl, create_post_use_case::CreatePostUseCaseImpl,
|
||||
get_all_labels_use_case::GetAllLabelsUseCaseImpl,
|
||||
create_post_use_case::CreatePostUseCaseImpl,
|
||||
get_all_post_info_use_case::GetAllPostInfoUseCaseImpl,
|
||||
get_post_by_id_use_case::GetFullPostUseCaseImpl,
|
||||
get_post_by_semantic_id_use_case::GetPostBySemanticIdUseCaseImpl,
|
||||
update_label_use_case::UpdateLabelUseCaseImpl, update_post_use_case::UpdatePostUseCaseImpl,
|
||||
},
|
||||
framework::db::{
|
||||
label_db_service_impl::LabelDbServiceImpl, post_db_service_impl::PostDbServiceImpl,
|
||||
update_post_use_case::UpdatePostUseCaseImpl,
|
||||
},
|
||||
framework::db::post_db_service_impl::PostDbServiceImpl,
|
||||
};
|
||||
use sqlx::{Pool, Postgres};
|
||||
|
||||
@ -54,6 +61,7 @@ use crate::configuration::Configuration;
|
||||
pub struct Container {
|
||||
pub auth_controller: Arc<dyn AuthController>,
|
||||
pub image_controller: Arc<dyn ImageController>,
|
||||
pub label_controller: Arc<dyn LabelController>,
|
||||
pub post_controller: Arc<dyn PostController>,
|
||||
}
|
||||
|
||||
@ -87,13 +95,27 @@ impl Container {
|
||||
get_user_use_case,
|
||||
));
|
||||
|
||||
// Post
|
||||
let post_db_service = Arc::new(PostDbServiceImpl::new(db_pool.clone()));
|
||||
// Label
|
||||
let label_db_service = Arc::new(LabelDbServiceImpl::new(db_pool.clone()));
|
||||
|
||||
let post_repository = Arc::new(PostRepositoryImpl::new(post_db_service.clone()));
|
||||
let label_repository = Arc::new(LabelRepositoryImpl::new(label_db_service.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 label_controller = Arc::new(LabelControllerImpl::new(
|
||||
create_label_use_case,
|
||||
update_label_use_case,
|
||||
get_all_labels_use_case,
|
||||
));
|
||||
|
||||
// Post
|
||||
let post_db_service = Arc::new(PostDbServiceImpl::new(db_pool.clone()));
|
||||
|
||||
let post_repository = Arc::new(PostRepositoryImpl::new(post_db_service.clone()));
|
||||
|
||||
let get_all_post_info_use_case =
|
||||
Arc::new(GetAllPostInfoUseCaseImpl::new(post_repository.clone()));
|
||||
let get_post_by_id_use_case =
|
||||
@ -104,10 +126,6 @@ impl Container {
|
||||
));
|
||||
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()));
|
||||
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(
|
||||
get_all_post_info_use_case,
|
||||
@ -115,9 +133,6 @@ impl Container {
|
||||
get_post_by_semantic_id_use_case,
|
||||
create_post_use_case,
|
||||
update_post_use_case,
|
||||
create_label_use_case,
|
||||
update_label_use_case,
|
||||
get_all_labels_use_case,
|
||||
));
|
||||
|
||||
// Image
|
||||
@ -141,6 +156,7 @@ impl Container {
|
||||
Self {
|
||||
auth_controller,
|
||||
image_controller,
|
||||
label_controller,
|
||||
post_controller,
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ use actix_web::{
|
||||
};
|
||||
use auth::framework::web::auth_web_routes::configure_auth_routes;
|
||||
use image::framework::web::image_web_routes::configure_image_routes;
|
||||
use label::framework::web::label_web_routes::configure_label_routes;
|
||||
use openidconnect::reqwest;
|
||||
use post::framework::web::post_web_routes::configure_post_routes;
|
||||
use server::{
|
||||
@ -84,9 +85,11 @@ fn create_app(
|
||||
.wrap(session_middleware_builder.build())
|
||||
.app_data(web::Data::from(container.auth_controller))
|
||||
.app_data(web::Data::from(container.image_controller))
|
||||
.app_data(web::Data::from(container.label_controller))
|
||||
.app_data(web::Data::from(container.post_controller))
|
||||
.configure(configure_api_doc_routes)
|
||||
.configure(configure_auth_routes)
|
||||
.configure(configure_image_routes)
|
||||
.configure(configure_label_routes)
|
||||
.configure(configure_post_routes)
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Color } from '$lib/post/domain/entity/color';
|
||||
import { Color } from '$lib/label/domain/entity/color';
|
||||
import z from 'zod';
|
||||
|
||||
export const ColorResponseSchema = z.object({
|
@ -1,5 +1,5 @@
|
||||
import { ColorResponseDto, ColorResponseSchema } from '$lib/post/adapter/gateway/colorResponseDto';
|
||||
import { Label } from '$lib/post/domain/entity/label';
|
||||
import { ColorResponseDto, ColorResponseSchema } from '$lib/label/adapter/gateway/colorResponseDto';
|
||||
import { Label } from '$lib/label/domain/entity/label';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const LabelResponseSchema = z.object({
|
@ -1,4 +1,4 @@
|
||||
import type { Color } from '$lib/post/domain/entity/color';
|
||||
import type { Color } from '$lib/label/domain/entity/color';
|
||||
|
||||
export class ColorViewModel {
|
||||
readonly red: number;
|
@ -1,8 +1,8 @@
|
||||
import {
|
||||
ColorViewModel,
|
||||
type DehydratedColorProps,
|
||||
} from '$lib/post/adapter/presenter/colorViewModel';
|
||||
import type { Label } from '$lib/post/domain/entity/label';
|
||||
} from '$lib/label/adapter/presenter/colorViewModel';
|
||||
import type { Label } from '$lib/label/domain/entity/label';
|
||||
|
||||
export class LabelViewModel {
|
||||
readonly id: number;
|
@ -1,4 +1,4 @@
|
||||
import type { Color } from '$lib/post/domain/entity/color';
|
||||
import type { Color } from '$lib/label/domain/entity/color';
|
||||
|
||||
export class Label {
|
||||
readonly id: number;
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { LabelViewModel } from '$lib/post/adapter/presenter/labelViewModel';
|
||||
import type { LabelViewModel } from '$lib/label/adapter/presenter/labelViewModel';
|
||||
|
||||
const { label }: { label: LabelViewModel } = $props();
|
||||
</script>
|
@ -1,4 +1,4 @@
|
||||
import { LabelResponseDto, LabelResponseSchema } from '$lib/post/adapter/gateway/labelResponseDto';
|
||||
import { LabelResponseDto, LabelResponseSchema } from '$lib/label/adapter/gateway/labelResponseDto';
|
||||
import { PostInfo } from '$lib/post/domain/entity/postInfo';
|
||||
import z from 'zod';
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {
|
||||
LabelViewModel,
|
||||
type DehydratedLabelProps,
|
||||
} from '$lib/post/adapter/presenter/labelViewModel';
|
||||
} from '$lib/label/adapter/presenter/labelViewModel';
|
||||
import type { PostInfo } from '$lib/post/domain/entity/postInfo';
|
||||
|
||||
export class PostInfoViewModel {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { Label } from '$lib/post/domain/entity/label';
|
||||
import type { Label } from '$lib/label/domain/entity/label';
|
||||
|
||||
export class PostInfo {
|
||||
readonly id: number;
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { PostInfoViewModel } from '$lib/post/adapter/presenter/postInfoViewModel';
|
||||
import PostLabel from '$lib/post/framework/ui/PostLabel.svelte';
|
||||
import PostLabel from '$lib/label/framework/ui/PostLabel.svelte';
|
||||
|
||||
const { postInfo }: { postInfo: PostInfoViewModel } = $props();
|
||||
</script>
|
||||
|
@ -10,7 +10,7 @@
|
||||
import CreatePostDialog, {
|
||||
type CreatePostDialogFormParams,
|
||||
} from '$lib/post/framework/ui/CreatePostDialog.svelte';
|
||||
import PostLabel from '$lib/post/framework/ui/PostLabel.svelte';
|
||||
import PostLabel from '$lib/label/framework/ui/PostLabel.svelte';
|
||||
import { getContext, onMount } from 'svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { LabelViewModel } from '$lib/post/adapter/presenter/labelViewModel';
|
||||
import PostLabel from '$lib/post/framework/ui/PostLabel.svelte';
|
||||
import type { LabelViewModel } from '$lib/label/adapter/presenter/labelViewModel';
|
||||
import PostLabel from '$lib/label/framework/ui/PostLabel.svelte';
|
||||
|
||||
const { labels }: { labels: readonly LabelViewModel[] } = $props();
|
||||
</script>
|
||||
|
Loading…
x
Reference in New Issue
Block a user