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

Question types framework + ShortAnswer & MultiChoice implementations #483

Merged
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
6 changes: 3 additions & 3 deletions backend/migrations/20240406023149_create_users.sql
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ CREATE TABLE users (
degree_name TEXT,
degree_starting_year INTEGER,
role user_role NOT NULL,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE UNIQUE INDEX IDX_users_email_lower on users ((lower(email)));
CREATE UNIQUE INDEX IDX_users_email_lower on users ((lower(email)));
6 changes: 3 additions & 3 deletions backend/migrations/20240406024211_create_organisations.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ CREATE TABLE organisations (
id BIGINT PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
logo TEXT,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE organisation_admins (
Expand All @@ -17,4 +17,4 @@ CREATE TABLE organisation_admins (
ON UPDATE CASCADE
);

CREATE INDEX IDX_organisation_admins_organisation on organisation_admins (organisation_id);
CREATE INDEX IDX_organisation_admins_organisation on organisation_admins (organisation_id);
18 changes: 9 additions & 9 deletions backend/migrations/20240406025537_create_campaigns.sql
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ CREATE TABLE campaigns (
description TEXT,
starts_at TIMESTAMPTZ NOT NULL,
ends_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT FK_campaigns_organisations
FOREIGN KEY(organisation_id)
REFERENCES organisations(id)
ON DELETE CASCADE
ON UPDATE CASCADE
FOREIGN KEY(organisation_id)
REFERENCES organisations(id)
ON DELETE CASCADE
ON UPDATE CASCADE
);

CREATE TABLE campaign_roles (
Expand All @@ -23,13 +23,13 @@ CREATE TABLE campaign_roles (
min_available INTEGER,
max_available INTEGER,
finalised BOOLEAN,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT FK_campaign_roles_campaign
FOREIGN KEY(campaign_id)
REFERENCES campaigns(id)
ON DELETE CASCADE
ON UPDATE CASCADE
);

CREATE INDEX IDX_campaign_roles_campaign on campaign_roles (campaign_id);
CREATE INDEX IDX_campaign_roles_campaign on campaign_roles (campaign_id);
19 changes: 10 additions & 9 deletions backend/migrations/20240406031400_create_questions.sql
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ CREATE TABLE questions (
id BIGINT PRIMARY KEY,
title TEXT NOT NULL,
description TEXT,
common BOOLEAN NOT NULL,
required BOOLEAN,
question_type question_type NOT NULL,
campaign_id BIGINT NOT NULL,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT FK_questions_campaigns
FOREIGN KEY(campaign_id)
REFERENCES campaigns(id)
Expand All @@ -17,14 +18,14 @@ CREATE TABLE questions (
);

CREATE TABLE multi_option_question_options (
id SERIAL PRIMARY KEY,
id BIGINT PRIMARY KEY,
text TEXT NOT NULL,
question_id INTEGER NOT NULL,
question_id BIGINT NOT NULL,
CONSTRAINT FK_multi_option_question_options_questions
FOREIGN KEY(question_id)
REFERENCES questions(id)
ON DELETE CASCADE
ON UPDATE CASCADE
FOREIGN KEY(question_id)
REFERENCES questions(id)
ON DELETE CASCADE
ON UPDATE CASCADE
);

CREATE INDEX IDX_multi_option_question_options_questions on multi_option_question_options (question_id);
CREATE INDEX IDX_multi_option_question_options_questions on multi_option_question_options (question_id);
64 changes: 32 additions & 32 deletions backend/migrations/20240406031915_create_applications.sql
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ CREATE TABLE applications (
user_id BIGINT NOT NULL,
status application_status NOT NULL,
private_status application_status NOT NULL,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT FK_applications_campaigns
FOREIGN KEY(campaign_id)
REFERENCES campaigns(id)
ON DELETE CASCADE
ON UPDATE CASCADE,
FOREIGN KEY(campaign_id)
REFERENCES campaigns(id)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT FK_applications_users
FOREIGN KEY(user_id)
REFERENCES users(id)
Expand Down Expand Up @@ -40,19 +40,19 @@ CREATE INDEX IDX_application_roles_applications on application_roles (applicatio
CREATE INDEX IDX_application_roles_campaign_roles on application_roles (campaign_role_id);

CREATE TABLE answers (
id SERIAL PRIMARY KEY,
application_id BIGINT NOT NULL,
question_id BIGINT NOT NULL,
CONSTRAINT FK_answers_applications
FOREIGN KEY(application_id)
REFERENCES applications(id)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT FK_answers_questions
FOREIGN KEY(question_id)
REFERENCES questions(id)
ON DELETE CASCADE
ON UPDATE CASCADE
id BIGINT PRIMARY KEY,
application_id BIGINT NOT NULL,
question_id BIGINT NOT NULL,
CONSTRAINT FK_answers_applications
FOREIGN KEY(application_id)
REFERENCES applications(id)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT FK_answers_questions
FOREIGN KEY(question_id)
REFERENCES questions(id)
ON DELETE CASCADE
ON UPDATE CASCADE
);

CREATE INDEX IDX_answers_applications on answers (application_id);
Expand All @@ -61,7 +61,7 @@ CREATE INDEX IDX_answers_questions on answers (question_id);
CREATE TABLE short_answer_answers (
id SERIAL PRIMARY KEY,
text TEXT NOT NULL,
answer_id INTEGER NOT NULL,
answer_id BIGINT NOT NULL,
CONSTRAINT FK_short_answer_answers_answers
FOREIGN KEY(answer_id)
REFERENCES answers(id)
Expand All @@ -74,17 +74,17 @@ CREATE INDEX IDX_short_answer_answers_answers on short_answer_answers (answer_id
CREATE TABLE multi_option_answer_options (
id SERIAL PRIMARY KEY,
option_id BIGINT NOT NULL,
answer_id INTEGER NOT NULL,
answer_id BIGINT NOT NULL,
CONSTRAINT FK_multi_option_answer_options_question_options
FOREIGN KEY(option_id)
REFERENCES multi_option_question_options(id)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT FK_multi_option_answer_options_answers
FOREIGN KEY(answer_id)
REFERENCES answers(id)
ON DELETE CASCADE
ON UPDATE CASCADE
FOREIGN KEY(answer_id)
REFERENCES answers(id)
ON DELETE CASCADE
ON UPDATE CASCADE
);

CREATE INDEX IDX_multi_option_answer_options_question_options on multi_option_answer_options (option_id);
Expand All @@ -95,13 +95,13 @@ CREATE TABLE application_ratings (
application_id BIGINT NOT NULL,
rater_id BIGINT NOT NULL,
rating INTEGER NOT NULL,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT FK_application_ratings_applications
FOREIGN KEY(application_id)
REFERENCES applications(id)
ON DELETE CASCADE
ON UPDATE CASCADE,
FOREIGN KEY(application_id)
REFERENCES applications(id)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT FK_application_ratings_users
FOREIGN KEY(rater_id)
REFERENCES users(id)
Expand All @@ -110,4 +110,4 @@ CREATE TABLE application_ratings (
);

CREATE INDEX IDX_application_ratings_applications on application_ratings (application_id);
CREATE INDEX IDX_application_ratings_users on application_ratings (rater_id);
CREATE INDEX IDX_application_ratings_users on application_ratings (rater_id);
2 changes: 1 addition & 1 deletion backend/server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ edition = "2021"
tokio = { version = "1.34", features = ["macros", "rt-multi-thread"] }
axum = { version = "0.7", features = ["macros"] }
axum-extra = { version = "0.9", features = ["typed-header"] }
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "time", "uuid"] }
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "time", "uuid", "chrono"] }

# Important secondary crates
anyhow = "1.0"
Expand Down
95 changes: 95 additions & 0 deletions backend/server/src/models/answer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use crate::models::error::ChaosError;
use anyhow::{bail, Result};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::{Pool, Postgres, QueryBuilder, Row};

/// The `Answer` type that will be sent in API responses.
///
///
/// With the chosen `serde` representation and the use of `#[serde(flatten)]`, the JSON for a
/// `Answer` will look like this:
/// ```json
/// {
/// "id": 7233828375289773948,
/// "application_id": 7233828375289125398,
/// "question_id": 7233828375289139200,
/// "answer_type": "MultiChoice",
/// "data": 7233828393325384908,
/// "created_at": "2024-06-28T16:29:04.644008111Z",
/// "updated_at": "2024-06-30T12:14:12.458390190Z"
/// }
/// ```
#[derive(Deserialize, Serialize)]
pub struct Answer {
id: i64,
application_id: i64,
question_id: i64,

#[serde(flatten)]
answer_data: AnswerData,

created_at: DateTime<Utc>,
updated_at: DateTime<Utc>,
}

#[derive(Deserialize, Serialize)]
#[serde( tag = "answer_type", content = "data")]
pub enum AnswerData {
ShortAnswer(String),
MultiChoice(i64),
MultiSelect(Vec<i64>),
DropDown(i64),
}

impl AnswerData {
pub async fn validate(self) -> Result<()> {
match self {
Self::ShortAnswer(text) => if text.len() <= 0 {bail!("Empty answer")},
Self::MultiSelect(data) => if data.len() <= 0 {bail!("Empty answer")},
_ => {},
}

Ok(())
}

pub async fn insert_into_db(self, answer_id: i64, pool: &Pool<Postgres>) -> Result<()> {
match self {
Self::ShortAnswer => {
let result = sqlx::query!(
"INSERT INTO short_answer_answers (text, answer_id) VALUES ($1, $2)",
self.text,
answer_id
)
.execute(pool)
.await?;

Ok(())
},
Self::MultiChoice(option_id)
| Self::DropDown(option_id) => {
let result = sqlx::query!(
"INSERT INTO multi_option_answer_options (option_id, answer_id) VALUES ($1, $2)",
option_id,
answer_id
)
.execute(pool)
.await?;

Ok(())
},
Self::MultiSelect(option_ids) => {
let mut query_builder = sqlx::QueryBuilder::new("INSERT INTO multi_option_answer_options (option_id, answer_id)");

query_builder.push_values(option_ids, |mut b, option_id| {
b.push_bind(option_id).push_bind(answer_id);
});

let query = query_builder.build();
let result = query.execute(pool).await?;

Ok(())
}
}
}
}
2 changes: 2 additions & 0 deletions backend/server/src/models/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
pub mod answer;
pub mod app;
pub mod auth;
pub mod error;
pub mod question;
pub mod user;
Loading
Loading