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

フォームに来た回答を時系列順にすべて取得するエントリポイントの実装 #287

Merged
merged 6 commits into from
Sep 16, 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
2 changes: 2 additions & 0 deletions server/Cargo.lock

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

2 changes: 1 addition & 1 deletion server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ serde_json = "1.0.107"
itertools = "0.11.0"
chrono = { version = "0.4.31" }
futures = "0.3.28"
uuid = "1.4.1"
uuid = { version = "1.4.1", features = ["v4"] }
deriving_via = "1.5.0"
reqwest = { version = "0.11.20", default-features = false, features = ["rustls"] }
num-traits = "0.2.16"
Expand Down
1 change: 1 addition & 0 deletions server/domain/src/repository/form_repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ pub trait FormRepository: Send + Sync + 'static {
form_update_targets: FormUpdateTargets,
) -> Result<(), Error>;
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>;
}
7 changes: 5 additions & 2 deletions server/entrypoint/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use presentation::{
auth::auth,
form_handler::{
create_form_handler, create_question_handler, delete_form_handler, form_list_handler,
get_form_handler, post_answer_handler, update_form_handler,
get_all_answers, get_form_handler, post_answer_handler, update_form_handler,
},
health_check_handler::health_check,
};
Expand Down Expand Up @@ -71,7 +71,10 @@ async fn main() -> anyhow::Result<()> {
.patch(update_form_handler),
)
.with_state(shared_repository.to_owned())
.route("/forms/answers", post(post_answer_handler))
.route(
"/forms/answers",
post(post_answer_handler).get(get_all_answers),
)
.with_state(shared_repository.to_owned())
.route("/forms/questions", post(create_question_handler))
.with_state(shared_repository.to_owned())
Expand Down
1 change: 1 addition & 0 deletions server/errors/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ edition = "2021"
sea-orm = { workspace = true }
strum = { workspace = true }
thiserror = "1.0.48"
uuid = { workspace = true }
5 changes: 5 additions & 0 deletions server/errors/src/infra.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ pub enum InfraError {
#[from]
source: sea_orm::error::DbErr,
},
#[error("Uuid Parse Error: {}", .source)]
UuidParse {
#[from]
source: uuid::Error,
},
#[error("Form Not Found: id = {}", .id)]
FormNotFound { id: i32 },
#[error("Outgoing Error: {}", .cause)]
Expand Down
15 changes: 15 additions & 0 deletions server/infra/entities/src/answers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use sea_orm::entity::prelude::*;
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub form_id: i32,
#[sea_orm(column_type = "Binary(BlobSize::Blob(Some(16)))")]
pub user: Vec<u8>,
pub title: String,
Expand All @@ -15,10 +16,24 @@ pub struct Model {

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::form_meta_data::Entity",
from = "Column::FormId",
to = "super::form_meta_data::Column::Id",
on_update = "NoAction",
on_delete = "NoAction"
)]
FormMetaData,
#[sea_orm(has_many = "super::real_answers::Entity")]
RealAnswers,
}

impl Related<super::form_meta_data::Entity> for Entity {
fn to() -> RelationDef {
Relation::FormMetaData.def()
}
}

impl Related<super::real_answers::Entity> for Entity {
fn to() -> RelationDef {
Relation::RealAnswers.def()
Expand Down
8 changes: 8 additions & 0 deletions server/infra/entities/src/form_meta_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ pub struct Model {

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::answers::Entity")]
Answers,
#[sea_orm(has_many = "super::default_answer_titles::Entity")]
DefaultAnswerTitles,
#[sea_orm(has_many = "super::form_questions::Entity")]
Expand All @@ -25,6 +27,12 @@ pub enum Relation {
ResponsePeriod,
}

impl Related<super::answers::Entity> for Entity {
fn to() -> RelationDef {
Relation::Answers.def()
}
}

impl Related<super::default_answer_titles::Entity> for Entity {
fn to() -> RelationDef {
Relation::DefaultAnswerTitles.def()
Expand Down
1 change: 1 addition & 0 deletions server/infra/resource/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ serde = { workspace = true }
tracing = { workspace = true }
num-traits = { workspace = true }
regex = { workspace = true }
uuid = { workspace = true }
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 @@ -6,7 +6,7 @@ use domain::form::models::{
use errors::infra::InfraError;
use mockall::automock;

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

#[async_trait]
pub trait DatabaseComponents: Send + Sync {
Expand Down Expand Up @@ -34,6 +34,7 @@ pub trait FormDatabase: Send + Sync {
form_update_targets: FormUpdateTargets,
) -> Result<(), InfraError>;
async fn post_answer(&self, answer: PostedAnswers) -> Result<(), InfraError>;
async fn get_all_answers(&self) -> Result<Vec<PostedAnswersDto>, InfraError>;
async fn create_questions(&self, questions: FormQuestionUpdateSchema)
-> Result<(), InfraError>;
}
46 changes: 44 additions & 2 deletions server/infra/resource/src/database/form.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use domain::form::models::{
use entities::{
answers, default_answer_titles, form_choices, form_meta_data, form_questions, form_webhooks,
prelude::{
DefaultAnswerTitles, FormChoices, FormMetaData, FormQuestions, FormWebhooks, RealAnswers,
Answers, DefaultAnswerTitles, FormChoices, FormMetaData, FormQuestions, FormWebhooks,
RealAnswers,
},
real_answers, response_period,
sea_orm_active_enums::QuestionType,
Expand All @@ -18,6 +19,7 @@ use itertools::Itertools;
use num_traits::cast::FromPrimitive;
use regex::Regex;
use sea_orm::{
prelude::Uuid,
sea_query::{Expr, SimpleExpr},
ActiveEnum, ActiveModelTrait, ActiveValue,
ActiveValue::Set,
Expand All @@ -26,7 +28,7 @@ use sea_orm::{

use crate::{
database::{components::FormDatabase, connection::ConnectionPool},
dto::{FormDto, QuestionDto},
dto::{AnswerDto, FormDto, PostedAnswersDto, QuestionDto},
};

#[async_trait]
Expand Down Expand Up @@ -405,6 +407,7 @@ impl FormDatabase for ConnectionPool {

let id = answers::ActiveModel {
id: Default::default(),
form_id: Set(answer.form_id.to_owned()),
user: Set(answer.uuid.to_owned().as_ref().to_vec()),
title: Set(embed_title),
time_stamp: Set(Utc::now()),
Expand All @@ -431,6 +434,45 @@ impl FormDatabase for ConnectionPool {
Ok(())
}

async fn get_all_answers(&self) -> Result<Vec<PostedAnswersDto>, InfraError> {
stream::iter(
Answers::find()
.order_by_desc(answers::Column::TimeStamp)
.all(&self.pool)
.await?,
)
.then(|answer| async move {
let answers = RealAnswers::find()
.filter(Expr::col(real_answers::Column::AnswerId).eq(answer.id))
.all(&self.pool)
.await?
.into_iter()
.map(
|entities::real_answers::Model {
question_id,
answer,
..
}| AnswerDto {
question_id,
answer,
},
)
.collect_vec();

Ok(PostedAnswersDto {
uuid: Uuid::from_slice(answer.user.as_slice())?,
timestamp: answer.time_stamp,
form_id: answer.form_id,
title: Some(answer.title),
answers,
})
})
.collect::<Vec<Result<PostedAnswersDto, _>>>()
.await
.into_iter()
.collect::<Result<Vec<PostedAnswersDto>, _>>()
}

async fn create_questions(
&self,
form_question_update_schema: FormQuestionUpdateSchema,
Expand Down
55 changes: 55 additions & 0 deletions server/infra/resource/src/dto.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use chrono::{DateTime, Utc};
use domain::form::models::{FormSettings, ResponsePeriod};
use uuid::Uuid;

pub struct QuestionDto {
pub id: i32,
Expand Down Expand Up @@ -79,3 +80,57 @@ impl TryFrom<FormDto> for domain::form::models::Form {
.build())
}
}

pub struct AnswerDto {
pub question_id: i32,
pub answer: String,
}

impl TryFrom<AnswerDto> for domain::form::models::Answer {
type Error = errors::domain::DomainError;

fn try_from(
AnswerDto {
question_id,
answer,
}: AnswerDto,
) -> Result<Self, Self::Error> {
Ok(domain::form::models::Answer {
question_id: question_id.into(),
answer,
})
}
}

pub struct PostedAnswersDto {
pub uuid: Uuid,
pub timestamp: DateTime<Utc>,
pub form_id: i32,
pub title: Option<String>,
pub answers: Vec<AnswerDto>,
}

impl TryFrom<PostedAnswersDto> for domain::form::models::PostedAnswers {
type Error = errors::domain::DomainError;

fn try_from(
PostedAnswersDto {
uuid,
timestamp,
form_id,
title,
answers,
}: PostedAnswersDto,
) -> Result<Self, Self::Error> {
Ok(domain::form::models::PostedAnswers {
uuid,
timestamp,
form_id: form_id.into(),
title: title.into(),
answers: answers
.into_iter()
.map(|answer| answer.try_into())
.collect::<Result<Vec<_>, _>>()?,
})
}
}
11 changes: 11 additions & 0 deletions server/infra/resource/src/repository/form_repository_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use domain::{
repository::form_repository::FormRepository,
};
use errors::Error;
use futures::{stream, stream::StreamExt};
use outgoing::form_outgoing;

use crate::{
Expand Down Expand Up @@ -77,6 +78,16 @@ impl<Client: DatabaseComponents + 'static> FormRepository for Repository<Client>
.map_err(Into::into)
}

#[tracing::instrument(skip(self))]
async fn get_all_answers(&self) -> Result<Vec<PostedAnswers>, Error> {
stream::iter(self.client.form().get_all_answers().await?)
.then(|posted_answers_dto| async { Ok(posted_answers_dto.try_into()?) })
.collect::<Vec<Result<PostedAnswers, _>>>()
.await
.into_iter()
.collect::<Result<Vec<PostedAnswers>, _>>()
}

async fn create_questions(&self, questions: FormQuestionUpdateSchema) -> Result<(), Error> {
self.client
.form()
Expand Down
13 changes: 12 additions & 1 deletion server/migration/src/m20230811_062425_create_answer_tables.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use sea_orm_migration::prelude::*;

use crate::m20221211_211233_form_questions::FormQuestionsTable;
use crate::{
m20220101_000001_create_table::FormMetaDataTable,
m20221211_211233_form_questions::FormQuestionsTable,
};

#[derive(DeriveMigrationName)]
pub struct Migration;
Expand All @@ -20,6 +23,13 @@ impl MigrationTrait for Migration {
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(AnswersTable::FormId).integer().not_null())
.foreign_key(
ForeignKey::create()
.name("fk-form-id-from-answers")
.from(AnswersTable::Answers, AnswersTable::FormId)
.to(FormMetaDataTable::FormMetaData, FormMetaDataTable::Id),
)
.col(ColumnDef::new(AnswersTable::User).uuid().not_null())
.col(
ColumnDef::new(AnswersTable::Title)
Expand Down Expand Up @@ -98,6 +108,7 @@ impl MigrationTrait for Migration {
enum AnswersTable {
Answers,
Id,
FormId,
User,
Title,
TimeStamp,
Expand Down
13 changes: 13 additions & 0 deletions server/presentation/src/form_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,19 @@ pub async fn update_form_handler(
}
}

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

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

pub async fn post_answer_handler(
State(repository): State<RealInfrastructureRepository>,
Json(answers): Json<PostedAnswers>,
Expand Down
4 changes: 4 additions & 0 deletions server/usecase/src/form.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ impl<R: FormRepository> FormUseCase<'_, R> {
self.repository.post_answer(answers).await
}

pub async fn get_all_answers(&self) -> Result<Vec<PostedAnswers>, Error> {
self.repository.get_all_answers().await
}

pub async fn create_questions(&self, questions: FormQuestionUpdateSchema) -> Result<(), Error> {
self.repository.create_questions(questions).await
}
Expand Down