From c1c7b121e48e661f30fe7411dc1bbfa88d510710 Mon Sep 17 00:00:00 2001 From: rito528 <39003544+rito528@users.noreply.github.com> Date: Wed, 23 Aug 2023 02:33:03 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20=E8=B3=AA=E5=95=8F=E3=82=92?= =?UTF-8?q?=E4=BD=9C=E6=88=90=E3=81=99=E3=82=8B=E3=82=A8=E3=83=B3=E3=83=88?= =?UTF-8?q?=E3=83=AA=E3=83=9D=E3=82=A4=E3=83=B3=E3=83=88=E3=81=AE=E5=AE=9F?= =?UTF-8?q?=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/domain/src/form/models.rs | 22 +++++++----- .../domain/src/repository/form_repository.rs | 4 ++- server/entrypoint/src/main.rs | 6 ++-- .../infra/resource/src/database/components.rs | 5 ++- server/infra/resource/src/database/form.rs | 34 +++++++++++++++++-- .../src/repository/form_repository_impl.rs | 12 ++++++- server/presentation/src/form_handler.rs | 18 +++++++++- server/usecase/src/form.rs | 8 ++++- 8 files changed, 92 insertions(+), 17 deletions(-) diff --git a/server/domain/src/form/models.rs b/server/domain/src/form/models.rs index 28f2e0d0..780129f3 100644 --- a/server/domain/src/form/models.rs +++ b/server/domain/src/form/models.rs @@ -6,7 +6,7 @@ use deriving_via::DerivingVia; #[cfg(test)] use proptest_derive::Arbitrary; use serde::{Deserialize, Serialize}; -use strum_macros::EnumString; +use strum_macros::{Display, EnumString}; use typed_builder::TypedBuilder; use uuid::Uuid; @@ -32,6 +32,12 @@ pub struct FormUpdateTargets { pub webhook: Option, } +#[derive(Deserialize, Debug)] +pub struct FormQuestionUpdateSchema { + pub form_id: FormId, + pub questions: Vec, +} + #[cfg_attr(test, derive(Arbitrary))] #[derive(DerivingVia, TypedBuilder, Clone, Getters, Debug, PartialOrd, PartialEq)] #[deriving(From, Into, IntoInner, Serialize(via: String), Deserialize(via: String))] @@ -72,17 +78,17 @@ pub type QuestionId = types::Id; #[cfg_attr(test, derive(Arbitrary))] #[derive(TypedBuilder, Serialize, Deserialize, Getters, Debug, PartialEq)] pub struct Question { - id: QuestionId, - title: String, - description: Option, - question_type: QuestionType, + pub id: QuestionId, + pub title: String, + pub description: Option, + pub question_type: QuestionType, #[cfg_attr(test, proptest(strategy = "arbitrary_with_size(1..100)"))] - choices: Vec, - is_required: bool, + pub choices: Vec, + pub is_required: bool, } #[cfg_attr(test, derive(Arbitrary))] -#[derive(Debug, Serialize, Deserialize, EnumString, PartialOrd, PartialEq)] +#[derive(Debug, Serialize, Deserialize, EnumString, PartialOrd, PartialEq, Display)] #[strum(ascii_case_insensitive)] pub enum QuestionType { TEXT, diff --git a/server/domain/src/repository/form_repository.rs b/server/domain/src/repository/form_repository.rs index 1dc64fb5..04d6c6ce 100644 --- a/server/domain/src/repository/form_repository.rs +++ b/server/domain/src/repository/form_repository.rs @@ -3,7 +3,8 @@ use errors::Error; use mockall::automock; use crate::form::models::{ - Form, FormDescription, FormId, FormTitle, FormUpdateTargets, OffsetAndLimit, PostedAnswers, + Form, FormDescription, FormId, FormQuestionUpdateSchema, FormTitle, FormUpdateTargets, + OffsetAndLimit, PostedAnswers, }; #[automock] @@ -20,4 +21,5 @@ pub trait FormRepository: Send + Sync + 'static { form_update_targets: FormUpdateTargets, ) -> Result<(), Error>; async fn post_answer(&self, answers: PostedAnswers) -> Result<(), Error>; + async fn create_questions(&self, questions: FormQuestionUpdateSchema) -> Result<(), Error>; } diff --git a/server/entrypoint/src/main.rs b/server/entrypoint/src/main.rs index 8f595009..8fef81f1 100644 --- a/server/entrypoint/src/main.rs +++ b/server/entrypoint/src/main.rs @@ -11,8 +11,8 @@ use hyper::header::AUTHORIZATION; use presentation::{ auth::auth, form_handler::{ - create_form_handler, delete_form_handler, form_list_handler, get_form_handler, - post_answer_handler, update_form_handler, + create_form_handler, create_question_handler, delete_form_handler, form_list_handler, + get_form_handler, post_answer_handler, update_form_handler, }, health_check_handler::health_check, }; @@ -73,6 +73,8 @@ async fn main() -> anyhow::Result<()> { .with_state(shared_repository.to_owned()) .route("/forms/answers", post(post_answer_handler)) .with_state(shared_repository.to_owned()) + .route("/forms/questions", post(create_question_handler)) + .with_state(shared_repository.to_owned()) .route("/health", get(health_check)) .layer(layer) .route_layer(middleware::from_fn(auth)) diff --git a/server/infra/resource/src/database/components.rs b/server/infra/resource/src/database/components.rs index bd07ee3a..db92540c 100644 --- a/server/infra/resource/src/database/components.rs +++ b/server/infra/resource/src/database/components.rs @@ -1,6 +1,7 @@ use async_trait::async_trait; use domain::form::models::{ - FormDescription, FormId, FormTitle, FormUpdateTargets, OffsetAndLimit, PostedAnswers, + FormDescription, FormId, FormQuestionUpdateSchema, FormTitle, FormUpdateTargets, + OffsetAndLimit, PostedAnswers, }; use errors::infra::InfraError; use mockall::automock; @@ -33,4 +34,6 @@ pub trait FormDatabase: Send + Sync { form_update_targets: FormUpdateTargets, ) -> Result<(), InfraError>; async fn post_answer(&self, answer: PostedAnswers) -> Result<(), InfraError>; + async fn create_questions(&self, questions: FormQuestionUpdateSchema) + -> Result<(), InfraError>; } diff --git a/server/infra/resource/src/database/form.rs b/server/infra/resource/src/database/form.rs index eb315498..02e26bb0 100644 --- a/server/infra/resource/src/database/form.rs +++ b/server/infra/resource/src/database/form.rs @@ -1,12 +1,14 @@ use async_trait::async_trait; use chrono::Utc; use domain::form::models::{ - FormDescription, FormId, FormTitle, FormUpdateTargets, OffsetAndLimit, PostedAnswers, + FormDescription, FormId, FormQuestionUpdateSchema, FormTitle, FormUpdateTargets, + OffsetAndLimit, PostedAnswers, }; use entities::{ answers, form_choices, form_meta_data, form_questions, form_webhooks, prelude::{FormChoices, FormMetaData, FormQuestions, FormWebhooks, RealAnswers}, real_answers, response_period, + sea_orm_active_enums::QuestionType, }; use errors::infra::{InfraError, InfraError::FormNotFound}; use futures::{stream, stream::StreamExt}; @@ -14,7 +16,7 @@ use itertools::Itertools; use num_traits::cast::FromPrimitive; use sea_orm::{ sea_query::{Expr, SimpleExpr}, - ActiveModelTrait, ActiveValue, + ActiveEnum, ActiveModelTrait, ActiveValue, ActiveValue::Set, ColumnTrait, EntityTrait, ModelTrait, QueryFilter, QueryOrder, QuerySelect, }; @@ -355,4 +357,32 @@ impl FormDatabase for ConnectionPool { Ok(()) } + + async fn create_questions( + &self, + questions: FormQuestionUpdateSchema, + ) -> Result<(), InfraError> { + let question_active_values = questions + .questions + .iter() + .map(|question| { + QuestionType::try_from_value(&question.question_type.to_string()).map( + |question_type| form_questions::ActiveModel { + question_id: ActiveValue::NotSet, + form_id: Set(questions.form_id.to_owned()), + title: Set(question.title.to_owned()), + description: Set(question.description.to_owned()), + question_type: Set(question_type), + is_required: Set(i8::from(question.is_required().to_owned())), + }, + ) + }) + .collect::, _>>()?; + + FormQuestions::insert_many(question_active_values) + .exec(&self.pool) + .await?; + + Ok(()) + } } diff --git a/server/infra/resource/src/repository/form_repository_impl.rs b/server/infra/resource/src/repository/form_repository_impl.rs index 018176a6..d4aa6b7d 100644 --- a/server/infra/resource/src/repository/form_repository_impl.rs +++ b/server/infra/resource/src/repository/form_repository_impl.rs @@ -1,7 +1,9 @@ use async_trait::async_trait; +use domain::form::models::OffsetAndLimit; use domain::{ form::models::{ - Form, FormDescription, FormId, FormTitle, FormUpdateTargets, OffsetAndLimit, PostedAnswers, + Form, FormDescription, FormId, FormQuestionUpdateSchema, FormTitle, FormUpdateTargets, + PostedAnswers, }, repository::form_repository::FormRepository, }; @@ -75,4 +77,12 @@ impl FormRepository for Repository .await .map_err(Into::into) } + + async fn create_questions(&self, questions: FormQuestionUpdateSchema) -> Result<(), Error> { + self.client + .form() + .create_questions(questions) + .await + .map_err(Into::into) + } } diff --git a/server/presentation/src/form_handler.rs b/server/presentation/src/form_handler.rs index aa9e670a..17fce1fc 100644 --- a/server/presentation/src/form_handler.rs +++ b/server/presentation/src/form_handler.rs @@ -5,7 +5,9 @@ use axum::{ Json, }; use domain::{ - form::models::{Form, FormId, FormUpdateTargets, OffsetAndLimit, PostedAnswers}, + form::models::{ + Form, FormId, FormQuestionUpdateSchema, FormUpdateTargets, OffsetAndLimit, PostedAnswers, + }, repository::Repositories, }; use errors::{infra::InfraError, Error}; @@ -122,6 +124,20 @@ pub async fn post_answer_handler( } } +pub async fn create_question_handler( + State(repository): State, + Json(questions): Json, +) -> impl IntoResponse { + let form_use_case = FormUseCase { + repository: repository.form_repository(), + }; + + match form_use_case.create_questions(questions).await { + Ok(_) => (StatusCode::OK).into_response(), + Err(err) => handle_error(err).into_response(), + } +} + pub fn handle_error(err: Error) -> impl IntoResponse { match err { Error::Infra { diff --git a/server/usecase/src/form.rs b/server/usecase/src/form.rs index dba736b2..87449e11 100644 --- a/server/usecase/src/form.rs +++ b/server/usecase/src/form.rs @@ -1,6 +1,8 @@ +use domain::form::models::OffsetAndLimit; use domain::{ form::models::{ - Form, FormDescription, FormId, FormTitle, FormUpdateTargets, OffsetAndLimit, PostedAnswers, + Form, FormDescription, FormId, FormQuestionUpdateSchema, FormTitle, FormUpdateTargets, + PostedAnswers, }, repository::form_repository::FormRepository, }; @@ -42,4 +44,8 @@ impl FormUseCase<'_, R> { pub async fn post_answers(&self, answers: PostedAnswers) -> Result<(), Error> { self.repository.post_answer(answers).await } + + pub async fn create_questions(&self, questions: FormQuestionUpdateSchema) -> Result<(), Error> { + self.repository.create_questions(questions).await + } } From f658857adb86c3615759dcfafc2fa0177d6ac526 Mon Sep 17 00:00:00 2001 From: rito528 <39003544+rito528@users.noreply.github.com> Date: Wed, 23 Aug 2023 14:32:31 +0900 Subject: [PATCH 2/6] =?UTF-8?q?fix:=20question=E3=81=AEid=E3=81=A8choices?= =?UTF-8?q?=E3=81=ABdefault=E3=82=92=E8=BF=BD=E5=8A=A0=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/domain/src/form/models.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/domain/src/form/models.rs b/server/domain/src/form/models.rs index 780129f3..57da45b6 100644 --- a/server/domain/src/form/models.rs +++ b/server/domain/src/form/models.rs @@ -78,11 +78,13 @@ pub type QuestionId = types::Id; #[cfg_attr(test, derive(Arbitrary))] #[derive(TypedBuilder, Serialize, Deserialize, Getters, Debug, PartialEq)] pub struct Question { + #[serde(default)] pub id: QuestionId, pub title: String, pub description: Option, pub question_type: QuestionType, #[cfg_attr(test, proptest(strategy = "arbitrary_with_size(1..100)"))] + #[serde(default)] pub choices: Vec, pub is_required: bool, } From c06d14ff51adbbf06cb9ce40c999900ce172cf38 Mon Sep 17 00:00:00 2001 From: rito528 <39003544+rito528@users.noreply.github.com> Date: Wed, 23 Aug 2023 14:39:22 +0900 Subject: [PATCH 3/6] =?UTF-8?q?fix:=20question=5Ftype=E3=82=92lowercase?= =?UTF-8?q?=E3=81=AB=E5=BC=B7=E5=88=B6=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/infra/resource/src/database/form.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/server/infra/resource/src/database/form.rs b/server/infra/resource/src/database/form.rs index 02e26bb0..2cdcdce3 100644 --- a/server/infra/resource/src/database/form.rs +++ b/server/infra/resource/src/database/form.rs @@ -366,16 +366,15 @@ impl FormDatabase for ConnectionPool { .questions .iter() .map(|question| { - QuestionType::try_from_value(&question.question_type.to_string()).map( - |question_type| form_questions::ActiveModel { + QuestionType::try_from_value(&question.question_type.to_string().to_lowercase()) + .map(|question_type| form_questions::ActiveModel { question_id: ActiveValue::NotSet, form_id: Set(questions.form_id.to_owned()), title: Set(question.title.to_owned()), description: Set(question.description.to_owned()), question_type: Set(question_type), is_required: Set(i8::from(question.is_required().to_owned())), - }, - ) + }) }) .collect::, _>>()?; From 1e52e09ce350adf4d6e4e109960a9c265be44f3d Mon Sep 17 00:00:00 2001 From: rito528 <39003544+rito528@users.noreply.github.com> Date: Wed, 23 Aug 2023 17:46:18 +0900 Subject: [PATCH 4/6] style: questions -> form_question_update_schema --- server/infra/resource/src/database/form.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/infra/resource/src/database/form.rs b/server/infra/resource/src/database/form.rs index 2cdcdce3..4de4634d 100644 --- a/server/infra/resource/src/database/form.rs +++ b/server/infra/resource/src/database/form.rs @@ -360,16 +360,16 @@ impl FormDatabase for ConnectionPool { async fn create_questions( &self, - questions: FormQuestionUpdateSchema, + form_question_update_schema: FormQuestionUpdateSchema, ) -> Result<(), InfraError> { - let question_active_values = questions + let question_active_values = form_question_update_schema .questions .iter() .map(|question| { QuestionType::try_from_value(&question.question_type.to_string().to_lowercase()) .map(|question_type| form_questions::ActiveModel { question_id: ActiveValue::NotSet, - form_id: Set(questions.form_id.to_owned()), + form_id: Set(form_question_update_schema.form_id.to_owned()), title: Set(question.title.to_owned()), description: Set(question.description.to_owned()), question_type: Set(question_type), From 9842d6b349e74563374581c9585932b1f6f22b10 Mon Sep 17 00:00:00 2001 From: rito528 <39003544+rito528@users.noreply.github.com> Date: Wed, 23 Aug 2023 23:29:04 +0900 Subject: [PATCH 5/6] =?UTF-8?q?feat:=20=E8=B3=AA=E5=95=8F=E3=81=AE?= =?UTF-8?q?=E9=81=B8=E6=8A=9E=E8=82=A2=E3=82=92=E6=B0=B8=E7=B6=9A=E5=8C=96?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/infra/resource/src/database/form.rs | 29 +++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/server/infra/resource/src/database/form.rs b/server/infra/resource/src/database/form.rs index 4de4634d..85975730 100644 --- a/server/infra/resource/src/database/form.rs +++ b/server/infra/resource/src/database/form.rs @@ -378,7 +378,34 @@ impl FormDatabase for ConnectionPool { }) .collect::, _>>()?; - FormQuestions::insert_many(question_active_values) + let last_insert_id = FormQuestions::insert_many(question_active_values) + .exec(&self.pool) + .await? + .last_insert_id; + + let first_insert_id = last_insert_id - form_question_update_schema.questions.len() as i32; + + let question_ids: Vec<_> = (first_insert_id..last_insert_id).collect(); + + let choices_active_values = form_question_update_schema + .questions + .iter() + .zip(question_ids) + .flat_map(|(question, question_id)| { + question + .choices + .iter() + .cloned() + .map(|choice| form_choices::ActiveModel { + id: ActiveValue::NotSet, + question_id: Set(question_id), + choice: Set(choice), + }) + .collect_vec() + }) + .collect_vec(); + + FormChoices::insert_many(choices_active_values) .exec(&self.pool) .await?; From 1cccba5b510be797d65a1ba52418fa1c595415fe Mon Sep 17 00:00:00 2001 From: rito528 <39003544+rito528@users.noreply.github.com> Date: Thu, 24 Aug 2023 18:17:28 +0900 Subject: [PATCH 6/6] style: apply rustfmt --- server/infra/resource/src/repository/form_repository_impl.rs | 3 +-- server/usecase/src/form.rs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/server/infra/resource/src/repository/form_repository_impl.rs b/server/infra/resource/src/repository/form_repository_impl.rs index d4aa6b7d..9af10c10 100644 --- a/server/infra/resource/src/repository/form_repository_impl.rs +++ b/server/infra/resource/src/repository/form_repository_impl.rs @@ -1,9 +1,8 @@ use async_trait::async_trait; -use domain::form::models::OffsetAndLimit; use domain::{ form::models::{ Form, FormDescription, FormId, FormQuestionUpdateSchema, FormTitle, FormUpdateTargets, - PostedAnswers, + OffsetAndLimit, PostedAnswers, }, repository::form_repository::FormRepository, }; diff --git a/server/usecase/src/form.rs b/server/usecase/src/form.rs index 87449e11..78169c04 100644 --- a/server/usecase/src/form.rs +++ b/server/usecase/src/form.rs @@ -1,8 +1,7 @@ -use domain::form::models::OffsetAndLimit; use domain::{ form::models::{ Form, FormDescription, FormId, FormQuestionUpdateSchema, FormTitle, FormUpdateTargets, - PostedAnswers, + OffsetAndLimit, PostedAnswers, }, repository::form_repository::FormRepository, };