Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
3108842
assign questions to one or all roles
KavikaPalletenne Feb 2, 2023
329287f
fix passing in reference to PgConnection
KavikaPalletenne Feb 2, 2023
4fe45aa
Revert "fix passing in reference to PgConnection"
KavikaPalletenne Feb 3, 2023
5ff27d7
added basic migration
KavikaPalletenne Feb 15, 2023
809d1bf
feat(backend): changed questions to common or unique
KavikaPalletenne Feb 27, 2023
2654217
fix(backend): seed.rs updated to match new questions schema
KavikaPalletenne Feb 27, 2023
e662319
style: ran cargo fmt
KavikaPalletenne Feb 27, 2023
334ddd6
feat: add new file to accomodate future additions of new question typ…
Mar 5, 2023
5163b33
Merge pull request #1 from KavikaPalletenne/main
KavikaPalletenne Mar 9, 2023
2aa0981
Merge branch 'main' into CHAOS-204-new-question-types-framework
Mar 11, 2023
28a3b91
feat: added migration for updating questions table to accomodate ques…
Mar 29, 2023
6908e41
feat: changed question type default from NULL to ShortAnswer, added s…
Apr 1, 2023
3ee61b7
feat: changed Question Response to include question Data; added Data …
Apr 2, 2023
969d105
feat: changed answer endpoint to send AnswerResponse type (answer + d…
Apr 3, 2023
b9f2907
fix: merged updated main
May 7, 2023
4e4ac04
fix:merge chaos-183
May 7, 2023
327d9a0
create migration to rename question_types enum
KavikaPalletenne Jun 11, 2023
f94306c
create migration for new multi select-based question types
KavikaPalletenne Jun 11, 2023
1595e3d
change answer_type to NOT NULL
KavikaPalletenne Jun 14, 2023
8ddc1f1
change question_type to NOT NULL
KavikaPalletenne Jun 14, 2023
ce1dc2d
updated schema.rs to reflect new question/answer types
KavikaPalletenne Jun 14, 2023
c1d0212
feat(backend): multiple-select question family stubs
KavikaPalletenne Jun 21, 2023
05fbbf5
feat(backend): create new data types for question data input
KavikaPalletenne Jun 21, 2023
508ccf8
feat(backend): handle insertion of question data into DB
KavikaPalletenne Jun 21, 2023
e230f5b
feat(backend): updated schema.rs to handle new question types
KavikaPalletenne Jun 24, 2023
b181d33
feat(backend): insert and get question data from db
KavikaPalletenne Jun 24, 2023
13f0dfe
feat(backend): fix not searching for question using question id
KavikaPalletenne Jun 24, 2023
bad8666
feat(backend): answer and question data input/fetch
KavikaPalletenne Jul 19, 2023
86e7010
Merge branch 'CHAOS-208-multi-select-question-type' into CHAOS-204-ne…
KavikaPalletenne Jul 19, 2023
20da953
feat(fix-partial): partially fixed returns and queries in new questio…
KavikaPalletenne Jul 19, 2023
3529a3d
feat(backend): removed unneeded passing-in of question into insert_qu…
KavikaPalletenne Jul 21, 2023
c11dcef
feat(backend): getting/setting question data and answer data should b…
KavikaPalletenne Jul 21, 2023
eed7880
fix(backend): removed accidental inclusion of .idea folder
KavikaPalletenne Jul 21, 2023
4693185
Merge branch 'main' into CHAOS-204-new-question-types-framework
AlexMIaoPU Jul 26, 2023
063167a
feat(fix): delete unused src/schema.rs
KavikaPalletenne Jul 26, 2023
62f3053
feat(fix): remove unused returns
KavikaPalletenne Jul 26, 2023
818c234
feat(fix): remove deprecated comments in models.rs
KavikaPalletenne Jul 26, 2023
9e5f363
feat(fix): fix broken things from removing returns in insert answer/q…
KavikaPalletenne Jul 26, 2023
60c608f
Merge remote-tracking branch 'origin/CHAOS-204-new-question-types-fra…
KavikaPalletenne Jul 26, 2023
a93c77e
feat(fix): removed duplicated images module in lib.rs
KavikaPalletenne Jul 26, 2023
c3d4053
feat(fix): update seed.rs to include question_type field in NewQuesti…
KavikaPalletenne Oct 2, 2023
1b9b43f
Merge branch 'main' into CHAOS-204-new-question-types-framework
KavikaPalletenne Oct 2, 2023
f6ef94c
feat(fix): add missing import of diesel::sql_types::* for users table…
KavikaPalletenne Oct 2, 2023
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
8 changes: 8 additions & 0 deletions backend/diesel.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# For documentation on how to configure this file,
# see https://diesel.rs/guides/configuring-diesel-cli

[print_schema]
file = "src/schema.rs"

[migrations_directory]
dir = "migrations"
2 changes: 2 additions & 0 deletions backend/migrations/2023-02-08-081803_questions/down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE IF EXISTS questions
DROP COLUMN role_id;
10 changes: 10 additions & 0 deletions backend/migrations/2023-02-08-081803_questions/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
ALTER TABLE IF EXISTS questions
ADD COLUMN role_id INTEGER DEFAULT NULL;

-- Make questions currently assigned to one role, uncommon
UPDATE questions SET role_id=role_ids[1] WHERE array_length(role_ids, 1) = 1;

-- No need to change questions currently assigned to multiple roles, as role_id is NULL by default, thus making them common

ALTER TABLE IF EXISTS questions
DROP COLUMN role_ids;
6 changes: 6 additions & 0 deletions backend/migrations/2023-03-29-025501_questions/down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-- This file should undo anything in `up.sql`

ALTER TABLE IF EXISTS questions
DROP COLUMN question_type;

DROP TYPE question_types;
6 changes: 6 additions & 0 deletions backend/migrations/2023-03-29-025501_questions/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-- Your SQL goes here

CREATE TYPE question_types AS ENUM ('ShortAnswer', 'MultiSelect');

ALTER TABLE IF EXISTS questions
ADD COLUMN question_type question_types DEFAULT 'ShortAnswer';
3 changes: 3 additions & 0 deletions backend/migrations/2023-04-02-063755_answers/down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- This file should undo anything in `up.sql`
ALTER TABLE IF EXISTS answers
DROP COLUMN answer_type;
4 changes: 4 additions & 0 deletions backend/migrations/2023-04-02-063755_answers/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- Your SQL goes here

ALTER TABLE IF EXISTS answers
ADD COLUMN answer_type question_types DEFAULT 'ShortAnswer';
3 changes: 3 additions & 0 deletions backend/migrations/2023-05-16-105659_questions/down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- This file should undo anything in `up.sql`

ALTER TYPE question_type RENAME TO question_types
3 changes: 3 additions & 0 deletions backend/migrations/2023-05-16-105659_questions/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- Your SQL goes here

ALTER TYPE question_types RENAME TO question_type
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- This file should undo anything in `up.sql`

DROP TABLE short_answer_answers;

DROP TABLE multi_select_options;

DROP TABLE multi_select_answers;
42 changes: 42 additions & 0 deletions backend/migrations/2023-05-16-111625_new_answer_types/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
-- Your SQL goes here
CREATE TABLE short_answer_answers ( -- TODO: Need to seed this table with data from the current answers table
id SERIAL PRIMARY KEY,
text TEXT NOT NULL,
answer_id INT NOT NULL,
CONSTRAINT fk_short_answer_answer_parent_answer
FOREIGN KEY(answer_id)
REFERENCES answers(id)
ON DELETE CASCADE
ON UPDATE CASCADE
);


CREATE TABLE multi_select_options(
id SERIAL PRIMARY KEY,
text TEXT NOT NULL,
question_id INT NOT NULL,
CONSTRAINT fk_multi_select_option_parent_question
FOREIGN KEY(question_id)
REFERENCES questions(id)
ON DELETE CASCADE
ON UPDATE CASCADE
);

CREATE TABLE multi_select_answers (
id SERIAL PRIMARY KEY,
option_id INT NOT NULL,
answer_id INT NOT NULL,

CONSTRAINT fk_multi_select_answer_parent_answer
FOREIGN KEY(answer_id)
REFERENCES answers(id)
ON DELETE CASCADE
ON UPDATE CASCADE,

-- If the option is deleted, then the selection should be too
CONSTRAINT fk_multi_select_option
FOREIGN KEY(option_id)
REFERENCES multi_select_options(id)
ON DELETE CASCADE
ON UPDATE CASCADE
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE IF EXISTS answers
ALTER COLUMN answer_type DROP NOT NULL;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE IF EXISTS answers
ALTER COLUMN answer_type SET NOT NULL;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE IF EXISTS questions
ALTER COLUMN question_type DROP NOT NULL;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE IF EXISTS questions
ALTER COLUMN question_type SET NOT NULL;
10 changes: 7 additions & 3 deletions backend/seed_data/src/seed.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#![allow(unused_variables)]

use backend::database::models::*;
use backend::database::schema::{AdminLevel, ApplicationStatus, UserGender};
use backend::database::schema::{AdminLevel, ApplicationStatus, UserGender, QuestionType};
use backend::images::{save_image, try_decode_bytes};
use chrono::naive::NaiveDate;
use diesel::pg::PgConnection;
Expand Down Expand Up @@ -219,19 +219,22 @@ pub fn seed() {
let question_one = NewQuestion {
title: "What is the meaning of life?".to_string(),
max_bytes: 100,
role_ids: vec![senior_mentor_role.id],
role_id: Option::from(senior_mentor_role.id),
// role_ids: vec![senior_mentor_role.id],
required: false,
description: Some("Please ensure to go into great detail!".to_string()),
question_type: QuestionType::ShortAnswer,
}
.insert(&connection)
.expect("Failed to insert question");

let question_two = NewQuestion {
title: "Why do you want to be a Peer Mentor".to_string(),
max_bytes: 300,
role_ids: vec![senior_mentor_role.id, mentor_role.id],
role_id: None,
required: true,
description: Some("Please explain why you would like to be a peer mentor!".to_string()),
question_type: QuestionType::ShortAnswer,
}
.insert(&connection)
.expect("Failed to insert question");
Expand Down Expand Up @@ -268,6 +271,7 @@ pub fn seed() {
question_id: question_one.id,
application_id: application.id,
description: "42".to_string(),
answer_type: QuestionType::ShortAnswer,
}
.insert(&connection)
.expect("Failed to insert answer");
Expand Down
45 changes: 38 additions & 7 deletions backend/server/src/application.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use diesel::prelude::*;

use crate::database::{
use crate::{database::{
models::{
Answer, Application, Campaign, Comment, NewAnswer, NewApplication, NewRating,
OrganisationUser, Question, Rating, Role, User,
},
schema::ApplicationStatus,
Database,
};
}, question_types::AnswerData};
use crate::error::JsonErr;
use rocket::{
get,
Expand All @@ -16,6 +16,7 @@ use rocket::{
serde::{json::Json, Deserialize, Serialize},
FromForm,
};
use crate::question_types::AnswerDataInput;

#[derive(Serialize)]
pub enum ApplicationError {
Expand All @@ -28,6 +29,7 @@ pub enum ApplicationError {
QuestionNotFound,
InvalidInput,
CampaignEnded,
AnswerDataNotFound,
}

#[derive(Deserialize)]
Expand Down Expand Up @@ -119,13 +121,24 @@ pub async fn create_rating(
.await
}

#[post("/answer", data = "<answer>")]
#[derive(Serialize, Deserialize)]
pub struct AnswerWithData {
pub answer: NewAnswer,
pub data: AnswerDataInput,
}


#[post("/answer", data = "<answer_with_data>")]
pub async fn submit_answer(
user: User,
db: Database,
answer: Json<NewAnswer>,
answer_with_data: Json<AnswerWithData>,
) -> Result<Json<()>, JsonErr<ApplicationError>> {
db.run(move |conn| {
let answer_with_data = answer_with_data.into_inner();
let answer = answer_with_data.answer;
let data = answer_with_data.data;

let application = Application::get(answer.application_id, &conn)
.ok_or(JsonErr(ApplicationError::AppNotFound, Status::NotFound))?;
if application.user_id != user.id {
Expand All @@ -140,19 +153,29 @@ pub async fn submit_answer(
return Err(JsonErr(ApplicationError::InvalidInput, Status::BadRequest));
}

NewAnswer::insert(&answer, &conn).ok_or(JsonErr(
let inserted_answer = NewAnswer::insert(&answer, &conn).ok_or(JsonErr(
ApplicationError::UnableToCreate,
Status::InternalServerError,
))?;


// Insert the Answer Data UwU
AnswerDataInput::insert_answer_data(data, conn, &inserted_answer);

Ok(Json(()))
})
.await
}

#[derive(Serialize)]
pub struct AnswersResponse {
answers: Vec<Answer>,
answers: Vec<AnswerResponse>,
}

#[derive(Serialize)]
pub struct AnswerResponse {
answer: Answer,
data: AnswerData,
}

#[get("/<application_id>/answers")]
Expand All @@ -170,8 +193,16 @@ pub async fn get_answers(
.check()
.map_err(|_| JsonErr(ApplicationError::Unauthorized, Status::Forbidden))?;

let mut response: Vec<AnswerResponse> = Vec::new();

for answer in Answer::get_all_from_application_id(&conn, application_id) {
let data = AnswerData::get_from_answer(&conn, &answer)
.ok_or(JsonErr(ApplicationError::AnswerDataNotFound, Status::NotFound))?;
response.push(AnswerResponse { answer: answer, data: data });
}

Ok(Json(AnswersResponse {
answers: Answer::get_all_from_application_id(conn, application_id),
answers: response,
}))
})
.await
Expand Down
50 changes: 36 additions & 14 deletions backend/server/src/campaigns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
Campaign, CampaignWithRoles, NewCampaignInput, NewQuestion, OrganisationUser, Role,
RoleUpdate, UpdateCampaignInput, User,
},
Database,
Database, schema::QuestionType,
},
images::{get_http_image_path, save_image, try_decode_data, ImageLocation},
};
Expand All @@ -15,6 +15,7 @@ use serde::{Deserialize, Serialize};
use std::fs::remove_file;

use uuid::Uuid;
use crate::question_types::QuestionDataInput;

#[derive(Serialize)]
pub enum CampaignError {
Expand Down Expand Up @@ -115,11 +116,14 @@ fn default_max_bytes() -> i32 {
#[derive(Serialize, Deserialize)]
pub struct QuestionInput {
pub title: String,
pub common_question: bool,
pub description: Option<String>,
#[serde(default = "default_max_bytes")]
pub max_bytes: i32,
#[serde(default)]
pub required: bool,
pub question_data: QuestionDataInput,
pub question_type: QuestionType,
}

#[derive(Deserialize)]
Expand All @@ -139,17 +143,25 @@ pub async fn new(
let NewCampaignWithData {
campaign,
roles,
questions,
mut questions,
} = inner;

let question_data: Vec<QuestionDataInput> = questions
.iter()
.map(|x| {
x.question_data.clone()
})
.collect();

let mut new_questions: Vec<NewQuestion> = questions
.into_iter()
.iter_mut()
.map(|x| NewQuestion {
role_ids: vec![],
title: x.title,
description: x.description,
role_id: None,
title: x.title.clone(),
description: x.description.clone(),
max_bytes: x.max_bytes,
required: x.required,
question_type: x.question_type,
})
.collect();

Expand Down Expand Up @@ -179,20 +191,30 @@ pub async fn new(
})?;

for question in role.questions_for_role {
if question < new_questions.len() {
new_questions[question].role_ids.push(inserted_role.id);
if questions[question].common_question {
// If question is common
new_questions[question].role_id = None;
} else if let None = new_questions[question].role_id {
// If question is unique and no role_id assigned to it
new_questions[question].role_id = Option::from(inserted_role.id);
} else {
// If question is meant to be unique, but already has a role_id assigned to it
eprintln!("Question is not common, yet has multiple roles asking for it");
return Err(JsonErr(CampaignError::UnableToCreate, Status::BadRequest));
}
}
}

for question in new_questions {
if question.role_ids.len() == 0 {
return Err(JsonErr(CampaignError::InvalidInput, Status::BadRequest));
}
question.insert(conn).ok_or_else(|| {
for (question, question_data) in new_questions.into_iter().zip(question_data.into_iter()) {

// Insert question (skeleton) into database, and then insert it's data into
// corresponding table in database.
let inserted_id = question.insert(conn).ok_or_else(|| {
eprintln!("Failed to create question for some reason");
JsonErr(CampaignError::UnableToCreate, Status::InternalServerError)
})?;
})?.id;

question_data.insert_question_data(conn, inserted_id);
}

Ok(Json(campaign))
Expand Down
Loading