diff --git a/server/domain/src/form/models.rs b/server/domain/src/form/models.rs index e41dc74d..c0371e1a 100644 --- a/server/domain/src/form/models.rs +++ b/server/domain/src/form/models.rs @@ -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")] diff --git a/server/domain/src/repository/form_repository.rs b/server/domain/src/repository/form_repository.rs index 7cf07dc1..611ba14a 100644 --- a/server/domain/src/repository/form_repository.rs +++ b/server/domain/src/repository/form_repository.rs @@ -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, limit: Option) -> Result, Error>; - async fn get(&self, id: FormId) -> Result, Error>; + async fn list( + &self, + offset: Option, + limit: Option, + ) -> Result>, Error>; + async fn get(&self, id: FormId) -> Result>, 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( diff --git a/server/infra/resource/src/repository/form_repository_impl.rs b/server/infra/resource/src/repository/form_repository_impl.rs index c13c06dc..b211cfa9 100644 --- a/server/infra/resource/src/repository/form_repository_impl.rs +++ b/server/infra/resource/src/repository/form_repository_impl.rs @@ -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::{ @@ -30,19 +31,34 @@ impl FormRepository for Repository } #[tracing::instrument(skip(self))] - async fn list(&self, offset: Option, limit: Option) -> Result, Error> { - let forms = self.client.form().list(offset, limit).await?; - forms + async fn list( + &self, + offset: Option, + limit: Option, + ) -> Result>, Error> { + self.client + .form() + .list(offset, limit) + .await? .into_iter() - .map(|form| form.try_into()) + .map(TryInto::
::try_into) + .map_ok(Into::>::into) + .map_ok(AuthorizationGuard::<_, Create>::into_read) .collect::, _>>() .map_err(Into::into) } #[tracing::instrument(skip(self))] - async fn get(&self, id: FormId) -> Result, 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>, Error> { + Ok(self + .client + .form() + .get(id) + .await? + .map(TryInto::::try_into) + .transpose()? + .map(Into::>::into) + .map(AuthorizationGuard::<_, Create>::into_read)) } #[tracing::instrument(skip(self))] @@ -153,7 +169,15 @@ impl FormRepository for Repository title: DefaultAnswerTitle, answers: Vec, ) -> Result<(), Error> { - match self.get(form_id).await? { + let form = self + .client + .form() + .get(form_id) + .await? + .map(TryInto::::try_into) + .transpose()?; + + match form { None => Ok(()), Some(form) => { let questions = self.get_questions(form_id).await?; @@ -286,7 +310,15 @@ impl FormRepository for Repository 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::::try_into) + .transpose()?; + + match form { None => Ok(()), Some(form) => { form_outgoing::post_comment(&form, comment, &posted_answers).await?; diff --git a/server/presentation/src/form_handler.rs b/server/presentation/src/form_handler.rs index 9b869b78..e4ef4f8b 100644 --- a/server/presentation/src/form_handler.rs +++ b/server/presentation/src/form_handler.rs @@ -77,6 +77,7 @@ pub async fn public_form_list_handler( } pub async fn form_list_handler( + Extension(user): Extension, State(repository): State, Query(offset_and_limit): Query, ) -> impl IntoResponse { @@ -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(), @@ -95,6 +96,7 @@ pub async fn form_list_handler( } pub async fn get_form_handler( + Extension(user): Extension, State(repository): State, Path(form_id): Path, ) -> impl IntoResponse { @@ -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(), } @@ -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(), }; @@ -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, @@ -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(), diff --git a/server/usecase/src/form.rs b/server/usecase/src/form.rs index f39ef2bc..2ffa2b5b 100644 --- a/server/usecase/src/form.rs +++ b/server/usecase/src/form.rs @@ -50,17 +50,26 @@ impl FormUseCase<'_, R1, R2> { pub async fn form_list( &self, + actor: &User, offset: Option, limit: Option, ) -> Result, 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::, _>>() + .map_err(Into::into) } - pub async fn get_form(&self, form_id: FormId) -> Result { + pub async fn get_form(&self, actor: &User, form_id: FormId) -> Result { 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> { @@ -143,10 +152,13 @@ impl FormUseCase<'_, R1, R2> { title: DefaultAnswerTitle, answers: Vec, ) -> 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(); @@ -270,7 +282,14 @@ impl 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 => { @@ -284,9 +303,10 @@ impl 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 } };