Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

フォームに紐づいた質問を取得するエンドポイントの実装 #324

Merged
merged 3 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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