Skip to content

Commit

Permalink
Merge pull request #268 from GiganticMinecraft/feat/createQuestion
Browse files Browse the repository at this point in the history
質問を作成するエントリポイントの実装
  • Loading branch information
rito528 authored Sep 8, 2023
2 parents 50128cb + 1cccba5 commit bcac02c
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 17 deletions.
24 changes: 16 additions & 8 deletions server/domain/src/form/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -32,6 +32,12 @@ pub struct FormUpdateTargets {
pub webhook: Option<WebhookUrl>,
}

#[derive(Deserialize, Debug)]
pub struct FormQuestionUpdateSchema {
pub form_id: FormId,
pub questions: Vec<Question>,
}

#[cfg_attr(test, derive(Arbitrary))]
#[derive(DerivingVia, TypedBuilder, Clone, Getters, Debug, PartialOrd, PartialEq)]
#[deriving(From, Into, IntoInner, Serialize(via: String), Deserialize(via: String))]
Expand Down Expand Up @@ -72,17 +78,19 @@ pub type QuestionId = types::Id<Question>;
#[cfg_attr(test, derive(Arbitrary))]
#[derive(TypedBuilder, Serialize, Deserialize, Getters, Debug, PartialEq)]
pub struct Question {
id: QuestionId,
title: String,
description: Option<String>,
question_type: QuestionType,
#[serde(default)]
pub id: QuestionId,
pub title: String,
pub description: Option<String>,
pub question_type: QuestionType,
#[cfg_attr(test, proptest(strategy = "arbitrary_with_size(1..100)"))]
choices: Vec<String>,
is_required: bool,
#[serde(default)]
pub choices: Vec<String>,
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,
Expand Down
4 changes: 3 additions & 1 deletion server/domain/src/repository/form_repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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>;
}
6 changes: 4 additions & 2 deletions server/entrypoint/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -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))
Expand Down
5 changes: 4 additions & 1 deletion server/infra/resource/src/database/components.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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>;
}
60 changes: 58 additions & 2 deletions server/infra/resource/src/database/form.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
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};
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,
};
Expand Down Expand Up @@ -355,4 +357,58 @@ impl FormDatabase for ConnectionPool {

Ok(())
}

async fn create_questions(
&self,
form_question_update_schema: FormQuestionUpdateSchema,
) -> Result<(), InfraError> {
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(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),
is_required: Set(i8::from(question.is_required().to_owned())),
})
})
.collect::<Result<Vec<_>, _>>()?;

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?;

Ok(())
}
}
11 changes: 10 additions & 1 deletion server/infra/resource/src/repository/form_repository_impl.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use async_trait::async_trait;
use domain::{
form::models::{
Form, FormDescription, FormId, FormTitle, FormUpdateTargets, OffsetAndLimit, PostedAnswers,
Form, FormDescription, FormId, FormQuestionUpdateSchema, FormTitle, FormUpdateTargets,
OffsetAndLimit, PostedAnswers,
},
repository::form_repository::FormRepository,
};
Expand Down Expand Up @@ -75,4 +76,12 @@ impl<Client: DatabaseComponents + 'static> FormRepository for Repository<Client>
.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)
}
}
18 changes: 17 additions & 1 deletion server/presentation/src/form_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -122,6 +124,20 @@ pub async fn post_answer_handler(
}
}

pub async fn create_question_handler(
State(repository): State<RealInfrastructureRepository>,
Json(questions): Json<FormQuestionUpdateSchema>,
) -> 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 {
Expand Down
7 changes: 6 additions & 1 deletion server/usecase/src/form.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use domain::{
form::models::{
Form, FormDescription, FormId, FormTitle, FormUpdateTargets, OffsetAndLimit, PostedAnswers,
Form, FormDescription, FormId, FormQuestionUpdateSchema, FormTitle, FormUpdateTargets,
OffsetAndLimit, PostedAnswers,
},
repository::form_repository::FormRepository,
};
Expand Down Expand Up @@ -42,4 +43,8 @@ impl<R: FormRepository> 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
}
}

0 comments on commit bcac02c

Please sign in to comment.