BLOG-105 Implement CRUD functionality for Labels #107

Merged
squid merged 3 commits from BLOG-105_lable_create_and_update_routes into main 2025-08-02 10:46:00 +08:00
33 changed files with 633 additions and 11 deletions

1
backend/Cargo.lock generated
View File

@ -2232,6 +2232,7 @@ version = "0.2.0"
dependencies = [
"actix-web",
"async-trait",
"auth",
"chrono",
"log",
"serde",

View File

@ -11,3 +11,5 @@ log.workspace = true
serde.workspace = true
sqlx.workspace = true
utoipa.workspace = true
auth.workspace = true

View File

@ -1,6 +1,9 @@
pub mod color_request_dto;
pub mod color_response_dto;
pub mod create_label_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;

View File

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

View File

@ -5,9 +5,16 @@ use crate::domain::entity::color::Color;
#[derive(Serialize, ToSchema)]
pub struct ColorResponseDto {
#[schema(maximum = 255)]
pub red: u8,
#[schema(maximum = 255)]
pub green: u8,
#[schema(maximum = 255)]
pub blue: u8,
#[schema(maximum = 255)]
pub alpha: u8,
}

View File

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

View File

@ -3,17 +3,25 @@ use std::sync::Arc;
use async_trait::async_trait;
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::{
error::post_error::PostError,
use_case::{
create_label_use_case::CreateLabelUseCase,
get_all_labels_use_case::GetAllLabelsUseCase,
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]
pub trait PostController: Send + Sync {
@ -23,21 +31,43 @@ pub trait PostController: Send + Sync {
) -> Result<Vec<PostInfoResponseDto>, 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 {
get_all_post_info_use_case: Arc<dyn GetAllPostInfoUseCase>,
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 {
pub fn new(
get_all_post_info_use_case: Arc<dyn GetAllPostInfoUseCase>,
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 {
get_all_post_info_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)
}
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()
})
}
}

View File

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

View File

@ -1,5 +1,7 @@
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;

View File

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

View File

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

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

View File

@ -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()
})
}
}

View File

@ -1 +1,2 @@
pub mod label_repository;
pub mod post_repository;

View File

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

View File

@ -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_full_post_use_case;
pub mod update_label_use_case;

View File

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

View File

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

View File

@ -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 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.update_label(label).await
}
}

View File

@ -1,3 +1,4 @@
#[derive(Clone)]
pub struct Color {
pub red: u8,
pub green: u8,

View File

@ -1,5 +1,6 @@
use crate::domain::entity::color::Color;
#[derive(Clone)]
pub struct Label {
pub id: i32,
pub name: String,

View File

@ -1,4 +1,6 @@
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;

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

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

View File

@ -1,5 +1,8 @@
pub mod post_api_doc;
pub mod post_web_routes;
mod create_label_handler;
mod get_all_labels_handler;
mod get_all_post_info_handler;
mod get_post_by_id_handler;
mod update_label_handler;

View File

@ -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()
}
}
}

View File

@ -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()
}
}
}

View File

@ -7,7 +7,7 @@ use crate::adapter::delivery::{
#[utoipa::path(
get,
path = "/post/all",
path = "/post",
tag = "post",
summary = "Get all post information",
params(

View File

@ -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};
#[derive(OpenApi)]
#[openapi(paths(
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;

View File

@ -1,14 +1,22 @@
use actix_web::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_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) {
cfg.service(
web::scope("/post")
.route("/all", web::get().to(get_all_post_info_handler))
.route("", web::get().to(get_all_post_info_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)),
);
}

View File

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

View File

@ -31,13 +31,20 @@ use openidconnect::reqwest;
use post::{
adapter::{
delivery::post_controller::{PostController, PostControllerImpl},
gateway::post_repository_impl::PostRepositoryImpl,
gateway::{
label_repository_impl::LabelRepositoryImpl, post_repository_impl::PostRepositoryImpl,
},
},
application::use_case::{
create_label_use_case::CreateLabelUseCaseImpl,
get_all_labels_use_case::GetAllLabelsUseCaseImpl,
get_all_post_info_use_case::GetAllPostInfoUseCaseImpl,
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};
@ -55,6 +62,7 @@ impl Container {
http_client: reqwest::Client,
configuration: Configuration,
) -> Self {
// Auth
let oidc_configuration = &configuration.oidc;
let auth_oidc_service = Arc::new(AuthOidcServiceImpl::new(
oidc_configuration.provider_metadata.clone(),
@ -64,40 +72,61 @@ impl Container {
http_client,
));
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 get_auth_url_use_case = Arc::new(LoginUseCaseImpl::new(auth_repository.clone()));
let exchange_auth_code_use_case =
Arc::new(ExchangeAuthCodeUseCaseImpl::new(auth_repository.clone()));
let get_user_use_case = Arc::new(GetUserUseCaseImpl::new(auth_repository.clone()));
let auth_controller = Arc::new(AuthControllerImpl::new(
get_auth_url_use_case,
exchange_auth_code_use_case,
get_user_use_case,
));
// Post
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 label_repository = Arc::new(LabelRepositoryImpl::new(label_db_service.clone()));
let get_all_post_info_use_case =
Arc::new(GetAllPostInfoUseCaseImpl::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(
get_all_post_info_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_storage = Arc::new(ImageStorageImpl::new(&configuration.storage.storage_path));
let image_repository = Arc::new(ImageRepositoryImpl::new(
image_db_service.clone(),
image_storage.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 image_controller = Arc::new(ImageControllerImpl::new(
upload_image_use_case,
get_image_use_case,
));
// Return the container with all controllers
Self {
auth_controller,
image_controller,

View File

@ -7,7 +7,7 @@ export class PostApiServiceImpl implements PostApiService {
constructor(private fetchFn: typeof fetch) {}
async getAllPosts(): Promise<PostInfoResponseDto[]> {
const url = new URL('post/all', Environment.API_BASE_URL);
const url = new URL('post', Environment.API_BASE_URL);
const response = await this.fetchFn(url.href);