Skip to content

Commit

Permalink
Merge pull request #324 from GiganticMinecraft/feat/getQuestionsHandler
Browse files Browse the repository at this point in the history
フォームに紐づいた質問を取得するエンドポイントの実装
  • Loading branch information
Mitama authored Oct 31, 2023
2 parents ccd14e0 + 99f06e6 commit 3895934
Show file tree
Hide file tree
Showing 11 changed files with 108 additions and 7 deletions.
1 change: 1 addition & 0 deletions server/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion server/domain/src/repository/form_repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use mockall::automock;
use crate::{
form::models::{
Form, FormDescription, FormId, FormQuestionUpdateSchema, FormTitle, FormUpdateTargets,
OffsetAndLimit, PostedAnswers, SimpleForm,
OffsetAndLimit, PostedAnswers, Question, SimpleForm,
},
user::models::User,
};
Expand All @@ -30,4 +30,5 @@ pub trait FormRepository: Send + Sync + 'static {
async fn post_answer(&self, answers: PostedAnswers) -> Result<(), Error>;
async fn get_all_answers(&self) -> Result<Vec<PostedAnswers>, Error>;
async fn create_questions(&self, questions: FormQuestionUpdateSchema) -> Result<(), Error>;
async fn get_questions(&self, form_id: FormId) -> Result<Vec<Question>, Error>;
}
1 change: 1 addition & 0 deletions server/domain/src/user/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ pub struct User {
pub enum Role {
Administrator,
#[default]
#[strum(serialize = "STANDARD_USER")]
StandardUser,
}
5 changes: 4 additions & 1 deletion server/entrypoint/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ use presentation::{
auth::auth,
form_handler::{
create_form_handler, create_question_handler, delete_form_handler, form_list_handler,
get_all_answers, get_form_handler, post_answer_handler, update_form_handler,
get_all_answers, get_form_handler, get_questions_handler, post_answer_handler,
update_form_handler,
},
health_check_handler::health_check,
};
Expand Down Expand Up @@ -69,6 +70,8 @@ async fn main() -> anyhow::Result<()> {
.patch(update_form_handler),
)
.with_state(shared_repository.to_owned())
.route("/forms/:id/questions", get(get_questions_handler))
.with_state(shared_repository.to_owned())
.route(
"/forms/answers",
post(post_answer_handler).get(get_all_answers),
Expand Down
3 changes: 2 additions & 1 deletion server/infra/resource/src/database/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use domain::{
use errors::infra::InfraError;
use mockall::automock;

use crate::dto::{FormDto, PostedAnswersDto, SimpleFormDto};
use crate::dto::{FormDto, PostedAnswersDto, QuestionDto, SimpleFormDto};

#[async_trait]
pub trait DatabaseComponents: Send + Sync {
Expand Down Expand Up @@ -46,6 +46,7 @@ pub trait FormDatabase: Send + Sync {
async fn get_all_answers(&self) -> Result<Vec<PostedAnswersDto>, InfraError>;
async fn create_questions(&self, questions: FormQuestionUpdateSchema)
-> Result<(), InfraError>;
async fn get_questions(&self, form_id: FormId) -> Result<Vec<QuestionDto>, InfraError>;
}

#[automock]
Expand Down
46 changes: 46 additions & 0 deletions server/infra/resource/src/database/form.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,4 +408,50 @@ impl FormDatabase for ConnectionPool {

Ok(())
}

async fn get_questions(&self, form_id: FormId) -> Result<Vec<QuestionDto>, InfraError> {
let questions_rs = self.query_all_and_values(
r"SELECT question_id, title, description, question_type, is_required FROM form_questions WHERE form_id = ?",
[form_id.to_owned().into()]
).await?;

let choices_rs = self
.query_all_and_values(
r"SELECT form_choices.question_id, choice FROM form_choices
INNER JOIN form_questions ON form_choices.question_id = form_questions.question_id
WHERE form_id = ?",
[form_id.to_owned().into()],
)
.await?;

questions_rs
.into_iter()
.map(|question_rs| {
let question_id: i32 = question_rs.try_get("", "question_id")?;

let choices = choices_rs
.iter()
.filter_map(|choice_rs| {
if choice_rs
.try_get::<i32>("", "question_id")
.is_ok_and(|id| id == question_id)
{
choice_rs.try_get::<String>("", "choice").ok()
} else {
None
}
})
.collect_vec();

Ok(QuestionDto {
id: question_id,
title: question_rs.try_get("", "title")?,
description: question_rs.try_get("", "description")?,
question_type: question_rs.try_get("", "question_type")?,
choices,
is_required: question_rs.try_get("", "is_required")?,
})
})
.collect::<Result<Vec<QuestionDto>, _>>()
}
}
16 changes: 15 additions & 1 deletion server/infra/resource/src/repository/form_repository_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use async_trait::async_trait;
use domain::{
form::models::{
Form, FormDescription, FormId, FormQuestionUpdateSchema, FormTitle, FormUpdateTargets,
OffsetAndLimit, PostedAnswers, SimpleForm,
OffsetAndLimit, PostedAnswers, Question, SimpleForm,
},
repository::form_repository::FormRepository,
user::models::User,
Expand Down Expand Up @@ -97,4 +97,18 @@ impl<Client: DatabaseComponents + 'static> FormRepository for Repository<Client>
.await
.map_err(Into::into)
}

async fn get_questions(&self, form_id: FormId) -> Result<Vec<Question>, Error> {
self.client
.form()
.get_questions(form_id)
.await
.map(|questions_dto| {
questions_dto
.into_iter()
.map(|question_dto| question_dto.try_into())
.collect::<Result<Vec<Question>, _>>()
})?
.map_err(Into::into)
}
}
1 change: 1 addition & 0 deletions server/presentation/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ tracing = { workspace = true }
usecase = { path = "../usecase" }
uuid = { workspace = true }
reqwest = { workspace = true }
regex = { workspace = true }
19 changes: 17 additions & 2 deletions server/presentation/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use domain::{
User,
},
};
use regex::Regex;
use reqwest::header::{ACCEPT, CONTENT_TYPE};
use resource::repository::RealInfrastructureRepository;
use usecase::user::UserUseCase;
Expand Down Expand Up @@ -54,10 +55,24 @@ pub async fn auth<B>(
.map_err(|_| StatusCode::UNAUTHORIZED)?
};

let standard_user_endpoints = [(&Method::GET, "/forms"), (&Method::POST, "/forms/answers")];
let static_endpoints_allowed_for_standard_users =
[(&Method::GET, "/forms"), (&Method::POST, "/forms/answers")];

// NOTE: 動的パスを指定する場合は、正規表現を埋め込む
let dynamic_endpoints_allowed_for_standard_users = [(&Method::GET, "/forms/[^/]+/questions")];

let is_not_allow_dynamic_endpoint = !dynamic_endpoints_allowed_for_standard_users
.into_iter()
.any(|(method, endpoint)| {
let regex = Regex::new(endpoint).unwrap();

method == request.method() && regex.is_match(request.uri().path())
});

if user.role == StandardUser
&& !standard_user_endpoints.contains(&(request.method(), request.uri().path()))
&& !static_endpoints_allowed_for_standard_users
.contains(&(request.method(), request.uri().path()))
&& is_not_allow_dynamic_endpoint
{
// NOTE: standard_user_endpointsに存在しないMethodとエンドポイントに
// 一般ユーザーがアクセスした場合は、アクセス権限なしとしてすべてFORBIDDENを返す。
Expand Down
14 changes: 14 additions & 0 deletions server/presentation/src/form_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,20 @@ pub async fn update_form_handler(
}
}

pub async fn get_questions_handler(
State(repository): State<RealInfrastructureRepository>,
Path(form_id): Path<FormId>,
) -> impl IntoResponse {
let form_use_case = FormUseCase {
repository: repository.form_repository(),
};

match form_use_case.get_questions(form_id).await {
Ok(questions) => (StatusCode::OK, Json(questions)).into_response(),
Err(err) => handle_error(err).into_response(),
}
}

pub async fn get_all_answers(
State(repository): State<RealInfrastructureRepository>,
) -> impl IntoResponse {
Expand Down
6 changes: 5 additions & 1 deletion server/usecase/src/form.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use domain::{
form::models::{
Form, FormDescription, FormId, FormQuestionUpdateSchema, FormTitle, FormUpdateTargets,
OffsetAndLimit, PostedAnswers, SimpleForm,
OffsetAndLimit, PostedAnswers, Question, SimpleForm,
},
repository::form_repository::FormRepository,
user::models::User,
Expand Down Expand Up @@ -37,6 +37,10 @@ impl<R: FormRepository> FormUseCase<'_, R> {
self.repository.delete(form_id).await
}

pub async fn get_questions(&self, form_id: FormId) -> Result<Vec<Question>, Error> {
self.repository.get_questions(form_id).await
}

pub async fn update_form(
&self,
form_id: FormId,
Expand Down

0 comments on commit 3895934

Please sign in to comment.