Skip to content

Commit

Permalink
refactor: Form の read まわりに AuthorizationGuard をつける
Browse files Browse the repository at this point in the history
  • Loading branch information
rito528 committed Dec 16, 2024
1 parent ae5643b commit c8ff91d
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 24 deletions.
3 changes: 2 additions & 1 deletion server/domain/src/form/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -921,9 +921,10 @@ impl Message {

#[cfg(test)]
mod test {
use super::*;
use test_case::test_case;

use super::*;

#[test_case("TEXT" => Ok(QuestionType::TEXT); "upper: TEXT")]
#[test_case("text" => Ok(QuestionType::TEXT); "lower: text")]
#[test_case("SINGLE" => Ok(QuestionType::SINGLE); "upper: SINGLE")]
Expand Down
8 changes: 6 additions & 2 deletions server/domain/src/repository/form_repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ use crate::{
#[async_trait]
pub trait FormRepository: Send + Sync + 'static {
async fn create(&self, form: &Form, user: &User) -> Result<(), Error>;
async fn list(&self, offset: Option<u32>, limit: Option<u32>) -> Result<Vec<Form>, Error>;
async fn get(&self, id: FormId) -> Result<Option<Form>, Error>;
async fn list(
&self,
offset: Option<u32>,
limit: Option<u32>,
) -> Result<Vec<AuthorizationGuard<Form, Read>>, Error>;
async fn get(&self, id: FormId) -> Result<Option<AuthorizationGuard<Form, Read>>, Error>;
async fn delete(&self, id: FormId) -> Result<(), Error>;
async fn update_title(&self, form_id: &FormId, title: &FormTitle) -> Result<(), Error>;
async fn update_description(
Expand Down
50 changes: 41 additions & 9 deletions server/infra/resource/src/repository/form_repository_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use domain::{
};
use errors::{domain::DomainError, infra::InfraError::AnswerNotFount, Error};
use futures::{stream, stream::StreamExt};
use itertools::Itertools;
use outgoing::form_outgoing;

use crate::{
Expand All @@ -30,19 +31,34 @@ impl<Client: DatabaseComponents + 'static> FormRepository for Repository<Client>
}

#[tracing::instrument(skip(self))]
async fn list(&self, offset: Option<u32>, limit: Option<u32>) -> Result<Vec<Form>, Error> {
let forms = self.client.form().list(offset, limit).await?;
forms
async fn list(
&self,
offset: Option<u32>,
limit: Option<u32>,
) -> Result<Vec<AuthorizationGuard<Form, Read>>, Error> {
self.client
.form()
.list(offset, limit)
.await?
.into_iter()
.map(|form| form.try_into())
.map(TryInto::<Form>::try_into)
.map_ok(Into::<AuthorizationGuard<Form, Create>>::into)
.map_ok(AuthorizationGuard::<_, Create>::into_read)
.collect::<Result<Vec<_>, _>>()
.map_err(Into::into)
}

#[tracing::instrument(skip(self))]
async fn get(&self, id: FormId) -> Result<Option<Form>, Error> {
let form = self.client.form().get(id).await?;
form.map(TryInto::try_into).transpose().map_err(Into::into)
async fn get(&self, id: FormId) -> Result<Option<AuthorizationGuard<Form, Read>>, Error> {
Ok(self
.client
.form()
.get(id)
.await?
.map(TryInto::<Form>::try_into)
.transpose()?
.map(Into::<AuthorizationGuard<Form, Create>>::into)
.map(AuthorizationGuard::<_, Create>::into_read))
}

#[tracing::instrument(skip(self))]
Expand Down Expand Up @@ -153,7 +169,15 @@ impl<Client: DatabaseComponents + 'static> FormRepository for Repository<Client>
title: DefaultAnswerTitle,
answers: Vec<FormAnswerContent>,
) -> Result<(), Error> {
match self.get(form_id).await? {
let form = self
.client
.form()
.get(form_id)
.await?
.map(TryInto::<Form>::try_into)
.transpose()?;

match form {
None => Ok(()),
Some(form) => {
let questions = self.get_questions(form_id).await?;
Expand Down Expand Up @@ -286,7 +310,15 @@ impl<Client: DatabaseComponents + 'static> FormRepository for Repository<Client>
id: answer_id.into_inner(),
})?;

match self.get(posted_answers.form_id).await? {
let form = self
.client
.form()
.get(posted_answers.form_id)
.await?
.map(TryInto::<Form>::try_into)
.transpose()?;

match form {
None => Ok(()),
Some(form) => {
form_outgoing::post_comment(&form, comment, &posted_answers).await?;
Expand Down
19 changes: 13 additions & 6 deletions server/presentation/src/form_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ pub async fn public_form_list_handler(
}

pub async fn form_list_handler(
Extension(user): Extension<User>,
State(repository): State<RealInfrastructureRepository>,
Query(offset_and_limit): Query<OffsetAndLimit>,
) -> impl IntoResponse {
Expand All @@ -86,7 +87,7 @@ pub async fn form_list_handler(
};

match form_use_case
.form_list(offset_and_limit.offset, offset_and_limit.limit)
.form_list(&user, offset_and_limit.offset, offset_and_limit.limit)
.await
{
Ok(forms) => (StatusCode::OK, Json(forms)).into_response(),
Expand All @@ -95,6 +96,7 @@ pub async fn form_list_handler(
}

pub async fn get_form_handler(
Extension(user): Extension<User>,
State(repository): State<RealInfrastructureRepository>,
Path(form_id): Path<FormId>,
) -> impl IntoResponse {
Expand All @@ -104,7 +106,7 @@ pub async fn get_form_handler(
};

// FIXME: form から questions を剥がしたので、usecase で questions を取得する必要がある
match form_use_case.get_form(form_id).await {
match form_use_case.get_form(&user, form_id).await {
Ok(form) => (StatusCode::OK, Json(form)).into_response(),
Err(err) => handle_error(err).into_response(),
}
Expand Down Expand Up @@ -213,7 +215,10 @@ pub async fn get_answer_handler(
};

if user.role == StandardUser {
let form = match form_use_case.get_form(answer_dto.form_answer.form_id).await {
let form = match form_use_case
.get_form(&user, answer_dto.form_answer.form_id)
.await
{
Ok(form) => form,
Err(err) => return handle_error(err).into_response(),
};
Expand Down Expand Up @@ -253,7 +258,7 @@ pub async fn get_answer_by_form_id_handler(
};

if user.role == StandardUser {
match form_use_case.get_form(form_id).await {
match form_use_case.get_form(&user, form_id).await {
Ok(form) if *form.settings().answer_visibility() == PRIVATE => {
return (
StatusCode::FORBIDDEN,
Expand Down Expand Up @@ -372,17 +377,19 @@ pub async fn post_form_comment(
notification_repository: repository.notification_repository(),
};

// FIXME: コメントは handler 側で作られるべきではないし、
// コメントの id がデータベースで降られるなら Option になるべき。
let comment = Comment {
// NOTE: コメントはデータベースで insert した後に id が振られるのでデフォルト値を入れておく
comment_id: Default::default(),
answer_id: comment_schema.answer_id,
content: comment_schema.content,
timestamp: chrono::Utc::now(),
commented_by: user,
commented_by: user.to_owned(),
};

match form_use_case
.post_comment(comment, comment_schema.answer_id)
.post_comment(&user, comment, comment_schema.answer_id)
.await
{
Ok(_) => StatusCode::OK.into_response(),
Expand Down
32 changes: 26 additions & 6 deletions server/usecase/src/form.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,26 @@ impl<R1: FormRepository, R2: NotificationRepository> FormUseCase<'_, R1, R2> {

pub async fn form_list(
&self,
actor: &User,
offset: Option<u32>,
limit: Option<u32>,
) -> Result<Vec<Form>, Error> {
self.form_repository.list(offset, limit).await
self.form_repository
.list(offset, limit)
.await?
.into_iter()
.map(|form| form.try_into_read(actor))
.collect::<Result<Vec<_>, _>>()
.map_err(Into::into)
}

pub async fn get_form(&self, form_id: FormId) -> Result<Form, Error> {
pub async fn get_form(&self, actor: &User, form_id: FormId) -> Result<Form, Error> {
self.form_repository
.get(form_id)
.await?
.ok_or(Error::from(FormNotFound))
.ok_or(Error::from(FormNotFound))?
.try_into_read(actor)
.map_err(Into::into)
}

pub async fn delete_form(&self, form_id: FormId) -> Result<(), Error> {
Expand Down Expand Up @@ -143,10 +152,13 @@ impl<R1: FormRepository, R2: NotificationRepository> FormUseCase<'_, R1, R2> {
title: DefaultAnswerTitle,
answers: Vec<FormAnswerContent>,
) -> Result<(), Error> {
// TODO: answers に対して AuthorizationGuard を実装する必要がある
let is_within_period = self
.form_repository
.get(form_id)
.await?
.map(|form| form.try_into_read(user))
.transpose()?
.and_then(|form| {
let response_period = form.settings().response_period();

Expand Down Expand Up @@ -270,7 +282,14 @@ impl<R1: FormRepository, R2: NotificationRepository> FormUseCase<'_, R1, R2> {
self.form_repository.put_questions(form_id, questions).await
}

pub async fn post_comment(&self, comment: Comment, answer_id: AnswerId) -> Result<(), Error> {
pub async fn post_comment(
&self,
actor: &User,
comment: Comment,
answer_id: AnswerId,
) -> Result<(), Error> {
// TODO: ドメイン知識が UseCase に紛れ込んでいる。
// Comment に対して AuthorizationGuard を実装する必要がある
let can_post_comment = match comment.commented_by.role {
Administrator => true,
StandardUser => {
Expand All @@ -284,9 +303,10 @@ impl<R1: FormRepository, R2: NotificationRepository> FormUseCase<'_, R1, R2> {
.form_repository
.get(answer.form_id)
.await?
.ok_or(FormNotFound)?;
.ok_or(FormNotFound)?
.try_into_read(actor)?;

form.settings().answer_visibility() == &PUBLIC
*form.settings().visibility() == PUBLIC
}
};

Expand Down

0 comments on commit c8ff91d

Please sign in to comment.