BLOG-119 Restricted access to unpublished posts #124

Merged
squid merged 3 commits from BLOG-119_restrict_unpublished_post_access into main 2025-08-06 22:13:55 +08:00
7 changed files with 52 additions and 15 deletions
Showing only changes of commit 03d8054cc1 - Show all commits

View File

@ -33,14 +33,23 @@ pub trait PostController: Send + Sync {
query: PostQueryDto,
) -> Result<Vec<PostInfoResponseDto>, PostError>;
async fn get_post_by_id(&self, id: i32) -> Result<PostResponseDto, PostError>;
async fn get_post_by_id(
&self,
id: i32,
user_id: Option<i32>,
) -> Result<PostResponseDto, PostError>;
async fn create_post(&self, post: CreatePostRequestDto) -> Result<PostResponseDto, PostError>;
async fn create_post(
&self,
post: CreatePostRequestDto,
user_id: i32,
) -> Result<PostResponseDto, PostError>;
async fn update_post(
&self,
id: i32,
post: UpdatePostRequestDto,
user_id: i32,
) -> Result<PostResponseDto, PostError>;
async fn create_label(
@ -110,8 +119,12 @@ impl PostController for PostControllerImpl {
})
}
async fn get_post_by_id(&self, id: i32) -> Result<PostResponseDto, PostError> {
let result = self.get_full_post_use_case.execute(id).await;
async fn get_post_by_id(
&self,
id: i32,
user_id: Option<i32>,
) -> Result<PostResponseDto, PostError> {
let result = self.get_full_post_use_case.execute(id, user_id).await;
result.map(PostResponseDto::from)
}
@ -154,7 +167,11 @@ impl PostController for PostControllerImpl {
})
}
async fn create_post(&self, post: CreatePostRequestDto) -> Result<PostResponseDto, PostError> {
async fn create_post(
&self,
post: CreatePostRequestDto,
user_id: i32,
) -> Result<PostResponseDto, PostError> {
let label_ids = post.label_ids.clone();
let post_entity = post.into_entity();
@ -163,13 +180,14 @@ impl PostController for PostControllerImpl {
.execute(post_entity, &label_ids)
.await?;
self.get_post_by_id(id).await
self.get_post_by_id(id, Some(user_id)).await
}
async fn update_post(
&self,
id: i32,
post: UpdatePostRequestDto,
user_id: i32,
) -> Result<PostResponseDto, PostError> {
let label_ids = post.label_ids.clone();
let post_entity = post.into_entity(id);
@ -178,6 +196,6 @@ impl PostController for PostControllerImpl {
.execute(post_entity, &label_ids)
.await?;
self.get_post_by_id(id).await
self.get_post_by_id(id, Some(user_id)).await
}
}

View File

@ -3,6 +3,7 @@ use std::fmt::Display;
#[derive(Debug)]
pub enum PostError {
NotFound,
Unauthorized,
Unexpected(anyhow::Error),
}
@ -10,6 +11,7 @@ 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::Unexpected(e) => write!(f, "Unexpected error: {}", e),
}
}

View File

@ -9,7 +9,7 @@ use crate::{
#[async_trait]
pub trait GetFullPostUseCase: Send + Sync {
async fn execute(&self, id: i32) -> Result<Post, PostError>;
async fn execute(&self, id: i32, user_id: Option<i32>) -> Result<Post, PostError>;
}
pub struct GetFullPostUseCaseImpl {
@ -24,7 +24,13 @@ impl GetFullPostUseCaseImpl {
#[async_trait]
impl GetFullPostUseCase for GetFullPostUseCaseImpl {
async fn execute(&self, id: i32) -> Result<Post, PostError> {
self.post_repository.get_post_by_id(id).await
async fn execute(&self, id: i32, user_id: Option<i32>) -> Result<Post, PostError> {
let post = self.post_repository.get_post_by_id(id).await?;
if post.info.published_time.is_none() && user_id.is_none() {
return Err(PostError::Unauthorized);
}
Ok(post)
}
}

View File

@ -26,9 +26,11 @@ use crate::{
pub async fn create_post_handler(
post_controller: web::Data<dyn PostController>,
post_dto: web::Json<CreatePostRequestDto>,
_: UserId,
user_id: UserId,
) -> impl Responder {
let result = post_controller.create_post(post_dto.into_inner()).await;
let result = post_controller
.create_post(post_dto.into_inner(), user_id.get())
.await;
match result {
Ok(post) => HttpResponse::Created().json(post),

View File

@ -1,4 +1,5 @@
use actix_web::{HttpResponse, Responder, web};
use auth::framework::web::auth_middleware::UserId;
use sentry::integrations::anyhow::capture_anyhow;
use crate::{
@ -19,14 +20,18 @@ use crate::{
pub async fn get_post_by_id_handler(
post_controller: web::Data<dyn PostController>,
path: web::Path<i32>,
user_id: Option<UserId>,
) -> impl Responder {
let id = path.into_inner();
let result = post_controller.get_post_by_id(id).await;
let result = post_controller
.get_post_by_id(id, user_id.map(|id| id.get()))
.await;
match result {
Ok(post) => HttpResponse::Ok().json(post),
Err(e) => match e {
PostError::NotFound => HttpResponse::NotFound().finish(),
PostError::Unauthorized => HttpResponse::Unauthorized().finish(),
PostError::Unexpected(e) => {
capture_anyhow(&e);
HttpResponse::InternalServerError().finish()

View File

@ -38,6 +38,7 @@ pub async fn update_label_handler(
Ok(label) => HttpResponse::Ok().json(label),
Err(e) => match e {
PostError::NotFound => HttpResponse::NotFound().finish(),
PostError::Unauthorized => HttpResponse::Unauthorized().finish(),
PostError::Unexpected(e) => {
capture_anyhow(&e);
HttpResponse::InternalServerError().finish()

View File

@ -27,15 +27,18 @@ pub async fn update_post_handler(
post_controller: web::Data<dyn PostController>,
path: web::Path<i32>,
post_dto: web::Json<UpdatePostRequestDto>,
_: UserId,
user_id: UserId,
) -> impl Responder {
let id = path.into_inner();
let result = post_controller.update_post(id, post_dto.into_inner()).await;
let result = post_controller
.update_post(id, post_dto.into_inner(), user_id.get())
.await;
match result {
Ok(post) => HttpResponse::Ok().json(post),
Err(e) => match e {
PostError::NotFound => HttpResponse::NotFound().finish(),
PostError::Unauthorized => HttpResponse::Unauthorized().finish(),
PostError::Unexpected(e) => {
capture_anyhow(&e);
HttpResponse::InternalServerError().finish()