BLOG-119 Restricted access to unpublished posts #124
@ -31,16 +31,26 @@ pub trait PostController: Send + Sync {
|
|||||||
async fn get_all_post_info(
|
async fn get_all_post_info(
|
||||||
&self,
|
&self,
|
||||||
query: PostQueryDto,
|
query: PostQueryDto,
|
||||||
|
user_id: Option<i32>,
|
||||||
) -> Result<Vec<PostInfoResponseDto>, PostError>;
|
) -> 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(
|
async fn update_post(
|
||||||
&self,
|
&self,
|
||||||
id: i32,
|
id: i32,
|
||||||
post: UpdatePostRequestDto,
|
post: UpdatePostRequestDto,
|
||||||
|
user_id: i32,
|
||||||
) -> Result<PostResponseDto, PostError>;
|
) -> Result<PostResponseDto, PostError>;
|
||||||
|
|
||||||
async fn create_label(
|
async fn create_label(
|
||||||
@ -94,10 +104,11 @@ impl PostController for PostControllerImpl {
|
|||||||
async fn get_all_post_info(
|
async fn get_all_post_info(
|
||||||
&self,
|
&self,
|
||||||
query: PostQueryDto,
|
query: PostQueryDto,
|
||||||
|
user_id: Option<i32>,
|
||||||
) -> Result<Vec<PostInfoResponseDto>, PostError> {
|
) -> Result<Vec<PostInfoResponseDto>, PostError> {
|
||||||
let result = self
|
let result = self
|
||||||
.get_all_post_info_use_case
|
.get_all_post_info_use_case
|
||||||
.execute(query.is_published_only.unwrap_or(true))
|
.execute(query.is_published_only.unwrap_or(true), user_id)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
result.map(|post_info_list| {
|
result.map(|post_info_list| {
|
||||||
@ -110,8 +121,12 @@ impl PostController for PostControllerImpl {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_post_by_id(&self, id: i32) -> Result<PostResponseDto, PostError> {
|
async fn get_post_by_id(
|
||||||
let result = self.get_full_post_use_case.execute(id).await;
|
&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)
|
result.map(PostResponseDto::from)
|
||||||
}
|
}
|
||||||
@ -154,7 +169,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 label_ids = post.label_ids.clone();
|
||||||
let post_entity = post.into_entity();
|
let post_entity = post.into_entity();
|
||||||
|
|
||||||
@ -163,13 +182,14 @@ impl PostController for PostControllerImpl {
|
|||||||
.execute(post_entity, &label_ids)
|
.execute(post_entity, &label_ids)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
self.get_post_by_id(id).await
|
self.get_post_by_id(id, Some(user_id)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_post(
|
async fn update_post(
|
||||||
&self,
|
&self,
|
||||||
id: i32,
|
id: i32,
|
||||||
post: UpdatePostRequestDto,
|
post: UpdatePostRequestDto,
|
||||||
|
user_id: i32,
|
||||||
) -> Result<PostResponseDto, PostError> {
|
) -> Result<PostResponseDto, PostError> {
|
||||||
let label_ids = post.label_ids.clone();
|
let label_ids = post.label_ids.clone();
|
||||||
let post_entity = post.into_entity(id);
|
let post_entity = post.into_entity(id);
|
||||||
@ -178,6 +198,6 @@ impl PostController for PostControllerImpl {
|
|||||||
.execute(post_entity, &label_ids)
|
.execute(post_entity, &label_ids)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
self.get_post_by_id(id).await
|
self.get_post_by_id(id, Some(user_id)).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ use std::fmt::Display;
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum PostError {
|
pub enum PostError {
|
||||||
NotFound,
|
NotFound,
|
||||||
|
Unauthorized,
|
||||||
Unexpected(anyhow::Error),
|
Unexpected(anyhow::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -10,6 +11,7 @@ impl Display for PostError {
|
|||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
PostError::NotFound => write!(f, "Post not found"),
|
PostError::NotFound => write!(f, "Post not found"),
|
||||||
|
PostError::Unauthorized => write!(f, "Unauthorized access to post"),
|
||||||
PostError::Unexpected(e) => write!(f, "Unexpected error: {}", e),
|
PostError::Unexpected(e) => write!(f, "Unexpected error: {}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,11 @@ use crate::{
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait GetAllPostInfoUseCase: Send + Sync {
|
pub trait GetAllPostInfoUseCase: Send + Sync {
|
||||||
async fn execute(&self, is_published_only: bool) -> Result<Vec<PostInfo>, PostError>;
|
async fn execute(
|
||||||
|
&self,
|
||||||
|
is_published_only: bool,
|
||||||
|
user_id: Option<i32>,
|
||||||
|
) -> Result<Vec<PostInfo>, PostError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GetAllPostInfoUseCaseImpl {
|
pub struct GetAllPostInfoUseCaseImpl {
|
||||||
@ -24,7 +28,15 @@ impl GetAllPostInfoUseCaseImpl {
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl GetAllPostInfoUseCase for GetAllPostInfoUseCaseImpl {
|
impl GetAllPostInfoUseCase for GetAllPostInfoUseCaseImpl {
|
||||||
async fn execute(&self, is_published_only: bool) -> Result<Vec<PostInfo>, PostError> {
|
async fn execute(
|
||||||
self.post_repository.get_all_post_info(is_published_only).await
|
&self,
|
||||||
|
is_published_only: bool,
|
||||||
|
user_id: Option<i32>,
|
||||||
|
) -> Result<Vec<PostInfo>, PostError> {
|
||||||
|
let is_published_only = is_published_only && user_id.is_some();
|
||||||
|
|
||||||
|
self.post_repository
|
||||||
|
.get_all_post_info(is_published_only)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ use crate::{
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait GetFullPostUseCase: Send + Sync {
|
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 {
|
pub struct GetFullPostUseCaseImpl {
|
||||||
@ -24,7 +24,13 @@ impl GetFullPostUseCaseImpl {
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl GetFullPostUseCase for GetFullPostUseCaseImpl {
|
impl GetFullPostUseCase for GetFullPostUseCaseImpl {
|
||||||
async fn execute(&self, id: i32) -> Result<Post, PostError> {
|
async fn execute(&self, id: i32, user_id: Option<i32>) -> Result<Post, PostError> {
|
||||||
self.post_repository.get_post_by_id(id).await
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,9 +26,11 @@ use crate::{
|
|||||||
pub async fn create_post_handler(
|
pub async fn create_post_handler(
|
||||||
post_controller: web::Data<dyn PostController>,
|
post_controller: web::Data<dyn PostController>,
|
||||||
post_dto: web::Json<CreatePostRequestDto>,
|
post_dto: web::Json<CreatePostRequestDto>,
|
||||||
_: UserId,
|
user_id: UserId,
|
||||||
) -> impl Responder {
|
) -> 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 {
|
match result {
|
||||||
Ok(post) => HttpResponse::Created().json(post),
|
Ok(post) => HttpResponse::Created().json(post),
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use actix_web::{HttpResponse, Responder, web};
|
use actix_web::{HttpResponse, Responder, web};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
use auth::framework::web::auth_middleware::UserId;
|
||||||
use sentry::integrations::anyhow::capture_anyhow;
|
use sentry::integrations::anyhow::capture_anyhow;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -15,6 +16,7 @@ use crate::{
|
|||||||
path = "/post",
|
path = "/post",
|
||||||
tag = "post",
|
tag = "post",
|
||||||
summary = "Get all post information",
|
summary = "Get all post information",
|
||||||
|
description = "`is_published_only` query is only available for authenticated users.",
|
||||||
params(
|
params(
|
||||||
PostQueryDto
|
PostQueryDto
|
||||||
),
|
),
|
||||||
@ -25,8 +27,11 @@ use crate::{
|
|||||||
pub async fn get_all_post_info_handler(
|
pub async fn get_all_post_info_handler(
|
||||||
post_controller: web::Data<dyn PostController>,
|
post_controller: web::Data<dyn PostController>,
|
||||||
query: web::Query<PostQueryDto>,
|
query: web::Query<PostQueryDto>,
|
||||||
|
user_id: Option<UserId>,
|
||||||
) -> impl Responder {
|
) -> impl Responder {
|
||||||
let result = post_controller.get_all_post_info(query.into_inner()).await;
|
let result = post_controller
|
||||||
|
.get_all_post_info(query.into_inner(), user_id.map(|id| id.get()))
|
||||||
|
.await;
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(post_info_list) => HttpResponse::Ok().json(post_info_list),
|
Ok(post_info_list) => HttpResponse::Ok().json(post_info_list),
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use actix_web::{HttpResponse, Responder, web};
|
use actix_web::{HttpResponse, Responder, web};
|
||||||
|
use auth::framework::web::auth_middleware::UserId;
|
||||||
use sentry::integrations::anyhow::capture_anyhow;
|
use sentry::integrations::anyhow::capture_anyhow;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -11,6 +12,7 @@ use crate::{
|
|||||||
path = "/post/{id}",
|
path = "/post/{id}",
|
||||||
tag = "post",
|
tag = "post",
|
||||||
summary = "Get post by ID",
|
summary = "Get post by ID",
|
||||||
|
description = "Only authenticated users can access unpublished posts.",
|
||||||
responses (
|
responses (
|
||||||
(status = 200, body = PostResponseDto),
|
(status = 200, body = PostResponseDto),
|
||||||
(status = 404, description = "Post not found")
|
(status = 404, description = "Post not found")
|
||||||
@ -19,14 +21,18 @@ use crate::{
|
|||||||
pub async fn get_post_by_id_handler(
|
pub async fn get_post_by_id_handler(
|
||||||
post_controller: web::Data<dyn PostController>,
|
post_controller: web::Data<dyn PostController>,
|
||||||
path: web::Path<i32>,
|
path: web::Path<i32>,
|
||||||
|
user_id: Option<UserId>,
|
||||||
) -> impl Responder {
|
) -> impl Responder {
|
||||||
let id = path.into_inner();
|
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 {
|
match result {
|
||||||
Ok(post) => HttpResponse::Ok().json(post),
|
Ok(post) => HttpResponse::Ok().json(post),
|
||||||
Err(e) => match e {
|
Err(e) => match e {
|
||||||
PostError::NotFound => HttpResponse::NotFound().finish(),
|
PostError::NotFound => HttpResponse::NotFound().finish(),
|
||||||
|
PostError::Unauthorized => HttpResponse::Unauthorized().finish(),
|
||||||
PostError::Unexpected(e) => {
|
PostError::Unexpected(e) => {
|
||||||
capture_anyhow(&e);
|
capture_anyhow(&e);
|
||||||
HttpResponse::InternalServerError().finish()
|
HttpResponse::InternalServerError().finish()
|
||||||
|
@ -38,6 +38,7 @@ pub async fn update_label_handler(
|
|||||||
Ok(label) => HttpResponse::Ok().json(label),
|
Ok(label) => HttpResponse::Ok().json(label),
|
||||||
Err(e) => match e {
|
Err(e) => match e {
|
||||||
PostError::NotFound => HttpResponse::NotFound().finish(),
|
PostError::NotFound => HttpResponse::NotFound().finish(),
|
||||||
|
PostError::Unauthorized => HttpResponse::Unauthorized().finish(),
|
||||||
PostError::Unexpected(e) => {
|
PostError::Unexpected(e) => {
|
||||||
capture_anyhow(&e);
|
capture_anyhow(&e);
|
||||||
HttpResponse::InternalServerError().finish()
|
HttpResponse::InternalServerError().finish()
|
||||||
|
@ -27,15 +27,18 @@ pub async fn update_post_handler(
|
|||||||
post_controller: web::Data<dyn PostController>,
|
post_controller: web::Data<dyn PostController>,
|
||||||
path: web::Path<i32>,
|
path: web::Path<i32>,
|
||||||
post_dto: web::Json<UpdatePostRequestDto>,
|
post_dto: web::Json<UpdatePostRequestDto>,
|
||||||
_: UserId,
|
user_id: UserId,
|
||||||
) -> impl Responder {
|
) -> impl Responder {
|
||||||
let id = path.into_inner();
|
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 {
|
match result {
|
||||||
Ok(post) => HttpResponse::Ok().json(post),
|
Ok(post) => HttpResponse::Ok().json(post),
|
||||||
Err(e) => match e {
|
Err(e) => match e {
|
||||||
PostError::NotFound => HttpResponse::NotFound().finish(),
|
PostError::NotFound => HttpResponse::NotFound().finish(),
|
||||||
|
PostError::Unauthorized => HttpResponse::Unauthorized().finish(),
|
||||||
PostError::Unexpected(e) => {
|
PostError::Unexpected(e) => {
|
||||||
capture_anyhow(&e);
|
capture_anyhow(&e);
|
||||||
HttpResponse::InternalServerError().finish()
|
HttpResponse::InternalServerError().finish()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user