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 all 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 @@ -10,8 +10,8 @@ CREATE TABLE users (
degree_name TEXT,
degree_starting_year INTEGER,
role user_role NOT NULL,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL
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)));
7 changes: 4 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 UUID,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE TYPE organisation_role AS ENUM ('User', 'Admin');
Expand All @@ -12,12 +12,13 @@ CREATE TABLE organisation_members (
id BIGSERIAL PRIMARY KEY,
organisation_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
role organisation_role DEFAULT 'User' NOT NULL,
role organisation_role NOT NULL DEFAULT 'User',
CONSTRAINT FK_organisation_members_organisation
FOREIGN KEY(organisation_id)
REFERENCES organisations(id)
ON DELETE CASCADE
ON UPDATE CASCADE
);


CREATE INDEX IDX_organisation_admins_organisation on organisation_members (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 NOT NULL,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL,
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 NOT NULL,
max_available INTEGER NOT NULL,
finalised BOOLEAN NOT NULL,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL,
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);
22 changes: 12 additions & 10 deletions backend/migrations/20240406031400_create_questions.sql
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
CREATE TYPE question_type AS ENUM ('ShortAnswer', 'MultiChoice', 'MultiSelect', 'DropDown');
CREATE TYPE question_type AS ENUM ('ShortAnswer', 'MultiChoice', 'MultiSelect', 'DropDown', 'Ranking');

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 NOT NULL,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL,
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,15 @@ CREATE TABLE questions (
);

CREATE TABLE multi_option_question_options (
id BIGSERIAL PRIMARY KEY,
id BIGINT PRIMARY KEY,
text TEXT NOT NULL,
question_id INTEGER NOT NULL,
question_id BIGINT NOT NULL,
display_order INTEGER 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);
57 changes: 37 additions & 20 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 DEFAULT 'Pending',
private_status application_status NOT NULL DEFAULT 'Pending',
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL,
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,7 +40,7 @@ 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 BIGSERIAL PRIMARY KEY,
id BIGINT PRIMARY KEY,
application_id BIGINT NOT NULL,
question_id BIGINT NOT NULL,
CONSTRAINT FK_answers_applications
Expand All @@ -61,7 +61,7 @@ CREATE INDEX IDX_answers_questions on answers (question_id);
CREATE TABLE short_answer_answers (
id BIGSERIAL 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,34 @@ CREATE INDEX IDX_short_answer_answers_answers on short_answer_answers (answer_id
CREATE TABLE multi_option_answer_options (
id BIGSERIAL 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 TABLE ranking_answer_rankings (
id BIGSERIAL PRIMARY KEY,
option_id BIGINT NOT NULL,
rank INTEGER NOT NULL,
answer_id BIGINT NOT NULL,
CONSTRAINT FK_ranking_answer_rankings_question_options
FOREIGN KEY(option_id)
REFERENCES multi_option_question_options(id)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT FK_ranking_answer_rankings_answers
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 +112,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 NOT NULL,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL,
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 +127,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);
110 changes: 110 additions & 0 deletions backend/server/src/models/answer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
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),
Ranking(Vec<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(text) => {
let result = sqlx::query!(
"INSERT INTO short_answer_answers (text, answer_id) VALUES ($1, $2)",
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(())
},
Self::Ranking(option_ids) => {
let mut query_builder = sqlx::QueryBuilder::new("INSERT INTO ranking_answer_rankings (option_id, rank, answer_id)");

let mut rank = 1;
query_builder.push_values(option_ids, |mut b, option_id| {
b.push_bind(option_id).push_bind(rank).push_bind(answer_id);
rank += 1;
});

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

Ok(())
}
}
}
}
3 changes: 3 additions & 0 deletions backend/server/src/models/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
pub mod answer;
pub mod app;
pub mod auth;
pub mod campaign;
pub mod error;
pub mod question;
pub mod organisation;
pub mod role;
pub mod storage;
pub mod transaction;
pub mod user;
pub mod application;

Loading
Loading