Skip to content
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: 1 addition & 1 deletion backend/database-seeding/src/seeder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ pub async fn seed_database(mut seeder: Seeder) {

let application_id_1 = Application::create(
campaign_id,
3,
2,
NewApplication {
applied_roles: vec![
ApplicationRole {
Expand Down
3 changes: 2 additions & 1 deletion backend/migrations/20240406024211_create_organisations.sql
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ CREATE TABLE organisations (
);

CREATE TYPE organisation_role AS ENUM ('User', 'Admin');
CREATE UNIQUE INDEX IDX_organisations_slug on organisations(slug);

CREATE TABLE organisation_members (
id BIGSERIAL PRIMARY KEY,
Expand All @@ -22,4 +23,4 @@ CREATE TABLE organisation_members (
);


CREATE INDEX IDX_organisation_admins_organisation on organisation_members(organisation_id);
CREATE INDEX IDX_organisation_members_organisation on organisation_members(organisation_id);
3 changes: 3 additions & 0 deletions backend/migrations/20240406025537_create_campaigns.sql
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ CREATE TABLE campaigns (
UNIQUE (organisation_id, slug)
);

CREATE INDEX IDX_campaigns_organisation on campaigns(organisation_id);
CREATE INDEX IDX_campaigns_slug on campaigns(slug);

CREATE TABLE campaign_roles (
id BIGINT PRIMARY KEY,
campaign_id BIGINT NOT NULL,
Expand Down
8 changes: 5 additions & 3 deletions backend/migrations/20240406031400_create_questions.sql
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ CREATE TABLE questions (
ON UPDATE CASCADE
);

CREATE INDEX IDX_questions_campaign on questions(campaign_id);

CREATE TABLE multi_option_question_options (
id BIGINT PRIMARY KEY,
text TEXT NOT NULL,
Expand All @@ -31,7 +33,7 @@ CREATE TABLE multi_option_question_options (
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_question on multi_option_question_options(question_id);

CREATE TABLE question_roles (
id BIGSERIAL PRIMARY KEY,
Expand All @@ -52,5 +54,5 @@ CREATE TABLE question_roles (
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);
CREATE INDEX IDX_question_roles_question on question_roles(question_id);
CREATE INDEX IDX_question_roles_role on question_roles(role_id);
27 changes: 17 additions & 10 deletions backend/migrations/20240406031915_create_applications.sql
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ CREATE TABLE applications (
FOREIGN KEY(user_id)
REFERENCES users(id)
ON DELETE CASCADE
ON UPDATE CASCADE
ON UPDATE CASCADE,
UNIQUE(user_id, campaign_id)
);

CREATE INDEX IDX_applications_campaign on applications(campaign_id);
CREATE INDEX IDX_applications_user on applications(user_id);

CREATE TABLE application_roles (
id BIGSERIAL PRIMARY KEY,
application_id BIGINT NOT NULL,
Expand All @@ -38,8 +42,8 @@ CREATE TABLE application_roles (
ON UPDATE CASCADE
);

CREATE INDEX IDX_application_roles_applications on application_roles (application_id);
CREATE INDEX IDX_application_roles_campaign_roles on application_roles (campaign_role_id);
CREATE INDEX IDX_application_roles_application on application_roles(application_id);
CREATE INDEX IDX_application_roles_campaign_role on application_roles(campaign_role_id);

CREATE TABLE answers (
id BIGINT PRIMARY KEY,
Expand All @@ -60,8 +64,8 @@ CREATE TABLE answers (
DEFERRABLE INITIALLY DEFERRED
);

CREATE INDEX IDX_answers_applications on answers (application_id);
CREATE INDEX IDX_answers_questions on answers (question_id);
CREATE INDEX IDX_answers_application on answers(application_id);
CREATE INDEX IDX_answers_question on answers(question_id);

CREATE TABLE short_answer_answers (
id BIGSERIAL PRIMARY KEY,
Expand All @@ -74,7 +78,7 @@ CREATE TABLE short_answer_answers (
ON UPDATE CASCADE
);

CREATE INDEX IDX_short_answer_answers_answers on short_answer_answers (answer_id);
CREATE INDEX IDX_short_answer_answers_answer on short_answer_answers(answer_id);

CREATE TABLE multi_option_answer_options (
id BIGSERIAL PRIMARY KEY,
Expand All @@ -93,6 +97,9 @@ CREATE TABLE multi_option_answer_options (
ON UPDATE CASCADE
);

CREATE INDEX IDX_multi_option_answer_options_question_option on multi_option_answer_options(option_id);
CREATE INDEX IDX_multi_option_answer_options_answer on multi_option_answer_options(answer_id);

CREATE TABLE ranking_answer_rankings (
id BIGSERIAL PRIMARY KEY,
option_id BIGINT NOT NULL,
Expand All @@ -111,8 +118,8 @@ CREATE TABLE ranking_answer_rankings (
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_ranking_answer_rankings_question_option on multi_option_answer_options(option_id);
CREATE INDEX IDX_ranking_answer_rankings_answer on multi_option_answer_options(answer_id);

CREATE TABLE application_ratings (
id BIGINT PRIMARY KEY,
Expand All @@ -134,5 +141,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_application on application_ratings(application_id);
CREATE INDEX IDX_application_ratings_user on application_ratings(rater_id);
4 changes: 3 additions & 1 deletion backend/migrations/20241124054711_email_templates.sql
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ CREATE TABLE email_templates (
ON DELETE CASCADE
ON UPDATE CASCADE,
UNIQUE (organisation_id, name)
);
);

CREATE INDEX IDX_email_templates_organisation on email_templates(organisation_id);
7 changes: 6 additions & 1 deletion backend/migrations/20241126113027_offers.sql
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,9 @@ CREATE TABLE offers (
REFERENCES campaign_roles(id)
ON DELETE CASCADE
ON UPDATE CASCADE
);
);

CREATE INDEX IDX_offers_campaign on offers(campaign_id);
CREATE INDEX IDX_offers_application on offers(application_id);
CREATE INDEX IDX_offers_email_template on offers(email_template_id);
CREATE INDEX IDX_offers_role on offers(role_id);
4 changes: 2 additions & 2 deletions backend/server/src/handler/answer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
//! - Managing role-specific answers

use crate::models::answer::{Answer, NewAnswer};
use crate::models::app::{AppMessage, AppState};
use crate::models::app::{AppMessage, AppState, IdMessage};
use crate::models::auth::{AnswerOwner, ApplicationOwner, ApplicationOwnerOrReviewer};
use crate::models::error::ChaosError;
use crate::models::transaction::DBTransaction;
Expand Down Expand Up @@ -57,7 +57,7 @@ impl AnswerHandler {

transaction.tx.commit().await?;

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

/// Retrieves all common answers for an application.
Expand Down
4 changes: 2 additions & 2 deletions backend/server/src/handler/organisation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
//! - Email template management
//! - Logo image handling

use crate::models::app::{AppMessage, AppState};
use crate::models::app::{AppMessage, AppState, IdMessage};
use crate::models::auth::SuperUser;
use crate::models::auth::{AuthUser, OrganisationAdmin};
use crate::models::campaign::{Campaign, NewCampaign};
Expand Down Expand Up @@ -419,7 +419,7 @@ impl OrganisationHandler {
.await?;

transaction.tx.commit().await?;
Ok((StatusCode::OK, Json(json!({ "id": new_campaign_id }))))
Ok((StatusCode::OK, Json(IdMessage { id: new_campaign_id })))
}

/// Checks if a campaign slug is available.
Expand Down
4 changes: 2 additions & 2 deletions backend/server/src/handler/question.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//! - Updating and deleting questions
//! - Managing role-specific and common questions

use crate::models::app::{AppMessage, AppState};
use crate::models::app::{AppMessage, AppState, IdMessage};
use crate::models::auth::{AuthUser, CampaignAdmin, QuestionAdmin};
use crate::models::error::ChaosError;
use crate::models::question::{NewQuestion, Question};
Expand Down Expand Up @@ -56,7 +56,7 @@ impl QuestionHandler {

transaction.tx.commit().await?;

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

/// Retrieves all questions for a specific role in a campaign.
Expand Down
5 changes: 5 additions & 0 deletions backend/server/src/models/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ impl<T: Serialize> IntoResponse for AppMessage<T> {
}
}

#[derive(Serialize)]
pub struct IdMessage {
#[serde(serialize_with = "crate::models::serde_string::serialize")]
pub id: i64,
}

#[derive(Clone)]
pub struct AppState {
Expand Down
6 changes: 4 additions & 2 deletions backend/server/src/models/campaign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use axum::extract::{FromRef, FromRequestParts, Path};
use axum::http::request::Parts;
use uuid::Uuid;
use crate::models::app::AppState;
use crate::service::campaign::assert_campaign_is_open;
use crate::service::campaign::{assert_campaign_is_open, create_proper_slug};
use super::{error::ChaosError, storage::Storage};
use std::env;

Expand Down Expand Up @@ -231,13 +231,15 @@ impl Campaign {
/// * `Result<(), ChaosError>` - Success if slug is available, error if not
pub async fn check_slug_availability(
organisation_id: i64,
slug: String,
mut slug: String,
transaction: &mut Transaction<'_, Postgres>,
) -> Result<(), ChaosError> {
if !slug.is_ascii() {
return Err(ChaosError::BadRequest);
}

slug = create_proper_slug(&slug);

let exists = sqlx::query!(
"
SELECT EXISTS(SELECT 1 FROM campaigns WHERE organisation_id = $1 AND slug = $2)
Expand Down
13 changes: 10 additions & 3 deletions backend/server/src/models/organisation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use snowflake::SnowflakeIdGenerator;
use sqlx::{FromRow, Postgres, Transaction};
use std::ops::DerefMut;
use uuid::Uuid;
use crate::service::campaign::create_proper_slug;

/// Represents an organisation in the database.
///
Expand Down Expand Up @@ -160,7 +161,7 @@ impl Organisation {
/// The slug must be ASCII-only.
pub async fn create(
admin_id: i64,
slug: String,
mut slug: String,
name: String,
snowflake_generator: &mut SnowflakeIdGenerator,
transaction: &mut Transaction<'_, Postgres>,
Expand All @@ -169,6 +170,8 @@ impl Organisation {
return Err(ChaosError::BadRequest);
}

slug = create_proper_slug(&slug);

let id = snowflake_generator.real_time_generate();

sqlx::query!(
Expand Down Expand Up @@ -212,13 +215,15 @@ impl Organisation {
/// # Note
/// The slug must be ASCII-only.
pub async fn check_slug_availability(
slug: String,
mut slug: String,
transaction: &mut Transaction<'_, Postgres>,
) -> Result<(), ChaosError> {
if !slug.is_ascii() {
return Err(ChaosError::BadRequest);
}

slug = create_proper_slug(&slug);

let exists = sqlx::query!(
"
SELECT EXISTS(SELECT 1 FROM organisations WHERE slug = $1)
Expand Down Expand Up @@ -623,7 +628,7 @@ impl Organisation {

pub async fn create_campaign(
organisation_id: i64,
slug: String,
mut slug: String,
name: String,
description: Option<String>,
starts_at: DateTime<Utc>,
Expand All @@ -635,6 +640,8 @@ impl Organisation {
return Err(ChaosError::BadRequest);
}

slug = create_proper_slug(&slug);

let new_campaign_id = snowflake_id_generator.real_time_generate();

sqlx::query!(
Expand Down
20 changes: 20 additions & 0 deletions backend/server/src/service/campaign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,24 @@ pub async fn assert_campaign_is_open(
}

Ok(())
}

pub fn create_proper_slug(input: &str) -> String {
let mut result = String::new();
let mut last_char_was_hyphen = false; // To handle consecutive non-alphanumeric chars

for c in input.chars() {
if c.is_alphanumeric() {
result.push(c);
last_char_was_hyphen = false;
} else {
if !last_char_was_hyphen {
result.push('-');
last_char_was_hyphen = true;
}
}
}

// Remove leading and trailing hyphens if necessary (optional, depending on desired behavior)
result.trim_matches('-').to_string().to_lowercase()
}
Loading
Loading