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 & Answer CRUD #526

Merged
merged 22 commits into from
Nov 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
80a2297
initially defer foreign key updates on questions and options
KavikaPalletenne Nov 12, 2024
f28ef22
add multi_choice_options unique constraint to (question_id, display_o…
KavikaPalletenne Nov 12, 2024
097dd1b
fix warnings
KavikaPalletenne Nov 12, 2024
02dddf0
`Question` model implementation
KavikaPalletenne Nov 12, 2024
d2b0673
rename "ratings" to "rating"
KavikaPalletenne Nov 12, 2024
4de6bc2
rename `RatingHandler::create_rating()`
KavikaPalletenne Nov 12, 2024
9623564
remove whitespace on indexes in migrations
KavikaPalletenne Nov 12, 2024
7a560e8
add roles support for questions
KavikaPalletenne Nov 12, 2024
3ed9515
Question CRUD
KavikaPalletenne Nov 12, 2024
7435103
fix `id` to `role_id` in filtering by campaign and role
KavikaPalletenne Nov 12, 2024
1dd865e
fix forgotten transaction commits
KavikaPalletenne Nov 12, 2024
6293a54
Answer CRUD
KavikaPalletenne Nov 12, 2024
c1d0acd
ran cargo format
KavikaPalletenne Nov 12, 2024
bedd8c8
use `fetch_one()` to check valid id
KavikaPalletenne Nov 13, 2024
4acb472
fix path not stating with `/`
KavikaPalletenne Nov 13, 2024
64e6ddc
add `cargo run` to CI/CD for rust
KavikaPalletenne Nov 13, 2024
f5302f7
remove `cargo run` from rust pipeline
KavikaPalletenne Nov 13, 2024
4f8de66
remove todo clarifying authZ
KavikaPalletenne Nov 13, 2024
1be68de
fix rater_id passed in json
KavikaPalletenne Nov 13, 2024
264ad63
commit Organisation member transactions
KavikaPalletenne Nov 14, 2024
53cfc53
move `Router` creation to `models/app.rs`
KavikaPalletenne Nov 14, 2024
979b4b4
change `QuestionType` `if`/`else` to `match` statement
KavikaPalletenne Nov 22, 2024
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: 5 additions & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ jobs:
echo "GOOGLE_CLIENT_ID=test" >> backend/.env
echo "GOOGLE_CLIENT_SECRET=test" >> backend/.env
echo "GOOGLE_REDIRECT_URI=http://localhost:3000/auth/callback" >> backend/.env
echo "ROCKET_DATABASES='{}'" >> backend/.env
echo "S3_BUCKET_NAME=chaos-storage" >> backend/.env
echo "S3_ACCESS_KEY=test_access_key" >> backend/.env
echo "S3_SECRET_KEY=test_secret_key" >> backend/.env
echo "S3_ENDPOINT=https://chaos-storage.s3.ap-southeast-1.amazonaws.com" >> backend/.env
echo "S3_REGION_NAME=ap-southeast-1" >> backend/.env
# selecting a toolchain either by action or manual `rustup` calls should happen
# before the plugin, as it uses the current rustc version as its cache key
- uses: actions-rs/toolchain@v1
Expand Down
2 changes: 1 addition & 1 deletion backend/migrations/20240406023149_create_users.sql
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ CREATE TABLE users (
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)));
2 changes: 1 addition & 1 deletion backend/migrations/20240406024211_create_organisations.sql
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ CREATE TABLE organisation_members (
);


CREATE INDEX IDX_organisation_admins_organisation on organisation_members (organisation_id);
CREATE INDEX IDX_organisation_admins_organisation on organisation_members(organisation_id);
2 changes: 1 addition & 1 deletion backend/migrations/20240406025537_create_campaigns.sql
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ CREATE TABLE campaign_roles (
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);
28 changes: 26 additions & 2 deletions backend/migrations/20240406031400_create_questions.sql
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ CREATE TABLE questions (
title TEXT NOT NULL,
description TEXT,
common BOOLEAN NOT NULL,
required BOOLEAN,
required BOOLEAN NOT NULL,
question_type question_type NOT NULL,
campaign_id BIGINT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
Expand All @@ -27,6 +27,30 @@ CREATE TABLE multi_option_question_options (
REFERENCES questions(id)
ON DELETE CASCADE
ON UPDATE CASCADE
DEFERRABLE INITIALLY DEFERRED,
UNIQUE (question_id, display_order)
);

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);

CREATE TABLE question_roles (
id BIGSERIAL PRIMARY KEY,
question_id BIGINT NOT NULL,
role_id BIGINT NOT NULL,
CONSTRAINT FK_question_roles_questions
FOREIGN KEY(question_id)
REFERENCES questions(id)
ON DELETE CASCADE
ON UPDATE CASCADE
DEFERRABLE INITIALLY DEFERRED,
CONSTRAINT FK_question_roles_roles
FOREIGN KEY(role_id)
REFERENCES campaign_roles(id)
ON DELETE CASCADE
ON UPDATE CASCADE
DEFERRABLE INITIALLY DEFERRED,
UNIQUE (question_id, role_id)
);

CREATE INDEX IDX_question_roles_questions on question_roles(question_id);
CREATE INDEX IDX_question_roles_roles on question_roles(role_id);
15 changes: 9 additions & 6 deletions backend/migrations/20240406031915_create_applications.sql
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ CREATE TABLE answers (
REFERENCES questions(id)
ON DELETE CASCADE
ON UPDATE CASCADE
DEFERRABLE INITIALLY DEFERRED
);

CREATE INDEX IDX_answers_applications on answers (application_id);
Expand All @@ -79,7 +80,8 @@ CREATE TABLE multi_option_answer_options (
FOREIGN KEY(option_id)
REFERENCES multi_option_question_options(id)
ON DELETE CASCADE
ON UPDATE CASCADE,
ON UPDATE CASCADE
DEFERRABLE INITIALLY DEFERRED,
CONSTRAINT FK_multi_option_answer_options_answers
FOREIGN KEY(answer_id)
REFERENCES answers(id)
Expand All @@ -96,16 +98,17 @@ CREATE TABLE ranking_answer_rankings (
FOREIGN KEY(option_id)
REFERENCES multi_option_question_options(id)
ON DELETE CASCADE
ON UPDATE CASCADE,
ON UPDATE CASCADE
DEFERRABLE INITIALLY DEFERRED,
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);
CREATE INDEX IDX_multi_option_answer_options_answers on multi_option_answer_options (answer_id);
CREATE INDEX IDX_multi_option_answer_options_question_options on multi_option_answer_options(option_id);
CREATE INDEX IDX_multi_option_answer_options_answers on multi_option_answer_options(answer_id);

CREATE TABLE application_ratings (
id BIGINT PRIMARY KEY,
Expand All @@ -127,5 +130,5 @@ CREATE TABLE application_ratings (
ON UPDATE CASCADE
);

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_applications on application_ratings(application_id);
CREATE INDEX IDX_application_ratings_users on application_ratings(rater_id);
87 changes: 87 additions & 0 deletions backend/server/src/handler/answer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use crate::models::answer::{Answer, NewAnswer};
use crate::models::app::AppState;
use crate::models::auth::{AnswerOwner, ApplicationOwner, AuthUser};
use crate::models::error::ChaosError;
use crate::models::transaction::DBTransaction;
use axum::extract::{Json, Path, State};
use axum::http::StatusCode;
use axum::response::IntoResponse;
use serde_json::json;

pub struct AnswerHandler;

impl AnswerHandler {
pub async fn create(
State(state): State<AppState>,
Path(path): Path<i64>,
user: AuthUser,
mut transaction: DBTransaction<'_>,
Json(data): Json<NewAnswer>,
) -> Result<impl IntoResponse, ChaosError> {
let id = Answer::create(
user.user_id,
data.application_id,
data.question_id,
data.answer_data,
state.snowflake_generator,
&mut transaction.tx,
)
.await?;

transaction.tx.commit().await?;

Ok((StatusCode::OK, Json(json!({"id": id}))))
}

pub async fn get_all_common_by_application(
Path(application_id): Path<i64>,
_owner: ApplicationOwner,
mut transaction: DBTransaction<'_>,
) -> Result<impl IntoResponse, ChaosError> {
let answers =
Answer::get_all_common_by_application(application_id, &mut transaction.tx).await?;

transaction.tx.commit().await?;

Ok((StatusCode::OK, Json(answers)))
}

pub async fn get_all_by_application_and_role(
Path((application_id, role_id)): Path<(i64, i64)>,
_owner: ApplicationOwner,
mut transaction: DBTransaction<'_>,
) -> Result<impl IntoResponse, ChaosError> {
let answers =
Answer::get_all_by_application_and_role(application_id, role_id, &mut transaction.tx)
.await?;

transaction.tx.commit().await?;

Ok((StatusCode::OK, Json(answers)))
}

pub async fn update(
Path(answer_id): Path<i64>,
_owner: AnswerOwner,
mut transaction: DBTransaction<'_>,
Json(data): Json<NewAnswer>,
) -> Result<impl IntoResponse, ChaosError> {
Answer::update(answer_id, data.answer_data, &mut transaction.tx).await?;

transaction.tx.commit().await?;

Ok((StatusCode::OK, "Successfully updated answer"))
}

pub async fn delete(
Path(answer_id): Path<i64>,
_owner: AnswerOwner,
mut transaction: DBTransaction<'_>,
) -> Result<impl IntoResponse, ChaosError> {
Answer::delete(answer_id, &mut transaction.tx).await?;

transaction.tx.commit().await?;

Ok((StatusCode::OK, "Successfully deleted answer"))
}
}
4 changes: 2 additions & 2 deletions backend/server/src/handler/application.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::models::app::AppState;
use crate::models::application::{Application, ApplicationStatus};
use crate::models::auth::{AuthUser, ApplicationAdmin};
use crate::models::auth::{ApplicationAdmin, AuthUser};
use crate::models::error::ChaosError;
use crate::models::transaction::DBTransaction;
use axum::extract::{Json, Path, State};
Expand Down Expand Up @@ -48,4 +48,4 @@ impl ApplicationHandler {
transaction.tx.commit().await?;
Ok((StatusCode::OK, Json(applications)))
}
}
}
9 changes: 8 additions & 1 deletion backend/server/src/handler/campaign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,14 @@ impl CampaignHandler {
mut transaction: DBTransaction<'_>,
Json(data): Json<NewApplication>,
) -> Result<impl IntoResponse, ChaosError> {
Application::create(id, user.user_id, data, state.snowflake_generator, &mut transaction.tx).await?;
Application::create(
id,
user.user_id,
data,
state.snowflake_generator,
&mut transaction.tx,
)
.await?;
transaction.tx.commit().await?;
Ok((StatusCode::OK, "Successfully created application"))
}
Expand Down
8 changes: 5 additions & 3 deletions backend/server/src/handler/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
pub mod answer;
pub mod application;
pub mod auth;
pub mod user;
pub mod campaign;
pub mod organisation;
pub mod ratings;
pub mod question;
pub mod rating;
pub mod role;
pub mod application;
pub mod user;
10 changes: 6 additions & 4 deletions backend/server/src/handler/organisation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,27 +91,29 @@ impl OrganisationHandler {
}

pub async fn remove_admin(
State(state): State<AppState>,
mut transaction: DBTransaction<'_>,
Path(id): Path<i64>,
_super_user: SuperUser,
Json(request_body): Json<AdminToRemove>,
) -> Result<impl IntoResponse, ChaosError> {
Organisation::remove_admin(id, request_body.user_id, &state.db).await?;
Organisation::remove_admin(id, request_body.user_id, &mut transaction.tx).await?;

transaction.tx.commit().await?;
Ok((
StatusCode::OK,
"Successfully removed member from organisation",
))
}

pub async fn remove_member(
State(state): State<AppState>,
mut transaction: DBTransaction<'_>,
Path(id): Path<i64>,
_admin: OrganisationAdmin,
Json(request_body): Json<AdminToRemove>,
) -> Result<impl IntoResponse, ChaosError> {
Organisation::remove_member(id, request_body.user_id, &state.db).await?;
Organisation::remove_member(id, request_body.user_id, &mut transaction.tx).await?;

transaction.tx.commit().await?;
Ok((
StatusCode::OK,
"Successfully removed member from organisation",
Expand Down
102 changes: 102 additions & 0 deletions backend/server/src/handler/question.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
use crate::models::app::AppState;
use crate::models::auth::{AuthUser, CampaignAdmin, QuestionAdmin};
use crate::models::error::ChaosError;
use crate::models::question::{NewQuestion, Question};
use crate::models::transaction::DBTransaction;
use axum::extract::{Json, Path, State};
use axum::http::StatusCode;
use axum::response::IntoResponse;
use serde_json::json;

pub struct QuestionHandler;

impl QuestionHandler {
pub async fn create(
State(state): State<AppState>,
Path(campaign_id): Path<i64>,
_admin: CampaignAdmin,
mut transaction: DBTransaction<'_>,
Json(data): Json<NewQuestion>,
) -> Result<impl IntoResponse, ChaosError> {
let id = Question::create(
campaign_id,
data.title,
data.description,
data.common,
data.roles,
data.required,
data.question_data,
state.snowflake_generator,
&mut transaction.tx,
)
.await?;

transaction.tx.commit().await?;

Ok((StatusCode::OK, Json(json!({"id": id}))))
}

pub async fn get_all_by_campaign_and_role(
Path((campaign_id, role_id)): Path<(i64, i64)>,
_user: AuthUser,
mut transaction: DBTransaction<'_>,
) -> Result<impl IntoResponse, ChaosError> {
let questions =
Question::get_all_by_campaign_and_role(campaign_id, role_id, &mut transaction.tx)
.await?;

transaction.tx.commit().await?;

Ok((StatusCode::OK, Json(questions)))
}

pub async fn get_all_common_by_campaign(
Path(campaign_id): Path<i64>,
_user: AuthUser,
mut transaction: DBTransaction<'_>,
) -> Result<impl IntoResponse, ChaosError> {
let questions =
Question::get_all_common_by_campaign(campaign_id, &mut transaction.tx).await?;

transaction.tx.commit().await?;

Ok((StatusCode::OK, Json(questions)))
}

pub async fn update(
State(state): State<AppState>,
Path(question_id): Path<i64>,
_admin: QuestionAdmin,
mut transaction: DBTransaction<'_>,
Json(data): Json<NewQuestion>,
) -> Result<impl IntoResponse, ChaosError> {
Question::update(
question_id,
data.title,
data.description,
data.common,
data.roles,
data.required,
data.question_data,
&mut transaction.tx,
state.snowflake_generator,
)
.await?;

transaction.tx.commit().await?;

Ok((StatusCode::OK, "Successfully updated question"))
}

pub async fn delete(
Path(question_id): Path<i64>,
_admin: QuestionAdmin,
mut transaction: DBTransaction<'_>,
) -> Result<impl IntoResponse, ChaosError> {
Question::delete(question_id, &mut transaction.tx).await?;

transaction.tx.commit().await?;

Ok((StatusCode::OK, "Successfully deleted question"))
}
}
Loading
Loading