diff --git a/backend/server/src/handler/answer.rs b/backend/server/src/handler/answer.rs index 34f3e0ea..065be1da 100644 --- a/backend/server/src/handler/answer.rs +++ b/backend/server/src/handler/answer.rs @@ -1,6 +1,6 @@ use crate::models::answer::{Answer, NewAnswer}; use crate::models::app::AppState; -use crate::models::auth::{AnswerOwner, ApplicationOwner, AuthUser}; +use crate::models::auth::{AnswerOwner, ApplicationOwner}; use crate::models::error::ChaosError; use crate::models::transaction::DBTransaction; use axum::extract::{Json, Path, State}; @@ -13,14 +13,14 @@ pub struct AnswerHandler; impl AnswerHandler { pub async fn create( State(state): State, - Path(path): Path, - user: AuthUser, + Path(application_id): Path, + _user: ApplicationOwner, mut transaction: DBTransaction<'_>, Json(data): Json, ) -> Result { + // TODO: Check whether the question is contained in the campaign being applied to let id = Answer::create( - user.user_id, - data.application_id, + application_id, data.question_id, data.answer_data, state.snowflake_generator, diff --git a/backend/server/src/handler/campaign.rs b/backend/server/src/handler/campaign.rs index 30316ee2..0e1db393 100644 --- a/backend/server/src/handler/campaign.rs +++ b/backend/server/src/handler/campaign.rs @@ -2,17 +2,16 @@ use crate::models; use crate::models::app::AppState; use crate::models::application::Application; use crate::models::application::NewApplication; -use crate::models::auth::{AuthUser, SuperUser}; +use crate::models::auth::AuthUser; use crate::models::auth::CampaignAdmin; -use crate::models::campaign::{Campaign}; +use crate::models::campaign::Campaign; use crate::models::error::ChaosError; +use crate::models::offer::Offer; use crate::models::role::{Role, RoleUpdate}; use crate::models::transaction::DBTransaction; use axum::extract::{Json, Path, State}; use axum::http::StatusCode; use axum::response::IntoResponse; -use crate::models::offer::Offer; -use crate::models::organisation::{Organisation, SlugCheck}; pub struct CampaignHandler; impl CampaignHandler { @@ -31,7 +30,8 @@ impl CampaignHandler { Path((organisation_slug, campaign_slug)): Path<(String, String)>, _user: AuthUser, ) -> Result { - let campaign = Campaign::get_by_slugs(organisation_slug, campaign_slug, &mut transaction.tx).await?; + let campaign = + Campaign::get_by_slugs(organisation_slug, campaign_slug, &mut transaction.tx).await?; transaction.tx.commit().await?; Ok((StatusCode::OK, Json(campaign))) } @@ -62,7 +62,8 @@ impl CampaignHandler { Path(id): Path, _admin: CampaignAdmin, ) -> Result { - let banner_url = Campaign::update_banner(id, &mut transaction.tx, &state.storage_bucket).await?; + let banner_url = + Campaign::update_banner(id, &mut transaction.tx, &state.storage_bucket).await?; transaction.tx.commit().await?; Ok((StatusCode::OK, Json(banner_url))) } @@ -133,9 +134,18 @@ impl CampaignHandler { State(state): State, _admin: CampaignAdmin, mut transaction: DBTransaction<'_>, - Json(data): Json + Json(data): Json, ) -> Result { - let _ = Offer::create(id, data.application_id, data.email_template_id, data.role_id, data.expiry, &mut transaction.tx, state.snowflake_generator).await?; + let _ = Offer::create( + id, + data.application_id, + data.email_template_id, + data.role_id, + data.expiry, + &mut transaction.tx, + state.snowflake_generator, + ) + .await?; transaction.tx.commit().await?; Ok((StatusCode::OK, "Successfully created offer")) @@ -144,7 +154,7 @@ impl CampaignHandler { pub async fn get_offers( mut transaction: DBTransaction<'_>, Path(id): Path, - _user: CampaignAdmin + _user: CampaignAdmin, ) -> Result { let offers = Offer::get_by_campaign(id, &mut transaction.tx).await?; transaction.tx.commit().await?; diff --git a/backend/server/src/handler/email_template.rs b/backend/server/src/handler/email_template.rs index c80557a9..e392834f 100644 --- a/backend/server/src/handler/email_template.rs +++ b/backend/server/src/handler/email_template.rs @@ -1,11 +1,11 @@ -use axum::extract::{Path, State, Json}; -use axum::http::StatusCode; -use axum::response::IntoResponse; use crate::models::app::AppState; use crate::models::auth::EmailTemplateAdmin; use crate::models::email_template::EmailTemplate; use crate::models::error::ChaosError; use crate::models::transaction::DBTransaction; +use axum::extract::{Json, Path, State}; +use axum::http::StatusCode; +use axum::response::IntoResponse; pub struct EmailTemplateHandler; impl EmailTemplateHandler { @@ -23,7 +23,7 @@ impl EmailTemplateHandler { _user: EmailTemplateAdmin, Path(id): Path, State(state): State, - Json(request_body): Json + Json(request_body): Json, ) -> Result { EmailTemplate::update(id, request_body.name, request_body.template, &state.db).await?; @@ -39,4 +39,4 @@ impl EmailTemplateHandler { Ok((StatusCode::OK, "Successfully delete email template")) } -} \ No newline at end of file +} diff --git a/backend/server/src/handler/mod.rs b/backend/server/src/handler/mod.rs index 037cbbc2..33675427 100644 --- a/backend/server/src/handler/mod.rs +++ b/backend/server/src/handler/mod.rs @@ -2,10 +2,10 @@ pub mod answer; pub mod application; pub mod auth; pub mod campaign; +pub mod email_template; +pub mod offer; pub mod organisation; pub mod question; pub mod rating; pub mod role; pub mod user; -pub mod email_template; -pub mod offer; diff --git a/backend/server/src/handler/offer.rs b/backend/server/src/handler/offer.rs index 6bb0fc9e..92297c63 100644 --- a/backend/server/src/handler/offer.rs +++ b/backend/server/src/handler/offer.rs @@ -1,11 +1,10 @@ -use axum::extract::{Path, Json}; -use axum::http::StatusCode; -use axum::response::IntoResponse; -use crate::models::auth::{AuthUser, CampaignAdmin, OfferAdmin, OfferRecipient}; +use crate::models::auth::{OfferAdmin, OfferRecipient}; use crate::models::error::ChaosError; use crate::models::offer::{Offer, OfferReply}; use crate::models::transaction::DBTransaction; - +use axum::extract::{Json, Path}; +use axum::http::StatusCode; +use axum::response::IntoResponse; pub struct OfferHandler; impl OfferHandler { @@ -23,7 +22,7 @@ impl OfferHandler { pub async fn delete( mut transaction: DBTransaction<'_>, Path(id): Path, - _user: OfferAdmin + _user: OfferAdmin, ) -> Result { Offer::delete(id, &mut transaction.tx).await?; transaction.tx.commit().await?; @@ -64,4 +63,4 @@ impl OfferHandler { Ok((StatusCode::OK, "Successfully sent offer")) } -} \ No newline at end of file +} diff --git a/backend/server/src/handler/organisation.rs b/backend/server/src/handler/organisation.rs index a2e80e0f..7950f1fb 100644 --- a/backend/server/src/handler/organisation.rs +++ b/backend/server/src/handler/organisation.rs @@ -2,14 +2,16 @@ use crate::models; use crate::models::app::AppState; use crate::models::auth::SuperUser; use crate::models::auth::{AuthUser, OrganisationAdmin}; +use crate::models::campaign::Campaign; +use crate::models::email_template::EmailTemplate; use crate::models::error::ChaosError; -use crate::models::organisation::{AdminToRemove, AdminUpdateList, NewOrganisation, Organisation, SlugCheck}; +use crate::models::organisation::{ + AdminToRemove, AdminUpdateList, NewOrganisation, Organisation, SlugCheck, +}; use crate::models::transaction::DBTransaction; use axum::extract::{Json, Path, State}; use axum::http::StatusCode; use axum::response::IntoResponse; -use crate::models::campaign::{Campaign}; -use crate::models::email_template::EmailTemplate; pub struct OrganisationHandler; @@ -163,7 +165,7 @@ impl OrganisationHandler { pub async fn create_campaign( Path(id): Path, - State(mut state): State, + State(state): State, _admin: OrganisationAdmin, Json(request_body): Json, ) -> Result { @@ -195,9 +197,9 @@ impl OrganisationHandler { pub async fn create_email_template( Path(id): Path, - State(mut state): State, + State(state): State, _admin: OrganisationAdmin, - Json(request_body): Json + Json(request_body): Json, ) -> Result { Organisation::create_email_template( id, @@ -205,7 +207,8 @@ impl OrganisationHandler { request_body.template, &state.db, state.snowflake_generator, - ).await?; + ) + .await?; Ok((StatusCode::OK, "Successfully created email template")) } @@ -213,7 +216,7 @@ impl OrganisationHandler { pub async fn get_all_email_templates( _user: OrganisationAdmin, Path(id): Path, - State(state): State + State(state): State, ) -> Result { let email_templates = EmailTemplate::get_all_by_organisation(id, &state.db).await?; diff --git a/backend/server/src/handler/rating.rs b/backend/server/src/handler/rating.rs index 2ec36960..5c93e203 100644 --- a/backend/server/src/handler/rating.rs +++ b/backend/server/src/handler/rating.rs @@ -1,7 +1,6 @@ use crate::models::app::AppState; use crate::models::auth::{ - ApplicationCreatorGivenApplicationId, ApplicationReviewerGivenApplicationId, - ApplicationReviewerGivenRatingId, RatingCreator, + ApplicationReviewerGivenApplicationId, ApplicationReviewerGivenRatingId, RatingCreator, }; use crate::models::error::ChaosError; use crate::models::rating::{NewRating, Rating}; diff --git a/backend/server/src/models/answer.rs b/backend/server/src/models/answer.rs index 670a7ba8..c9f51772 100644 --- a/backend/server/src/models/answer.rs +++ b/backend/server/src/models/answer.rs @@ -1,11 +1,9 @@ use crate::models::error::ChaosError; -use crate::models::question::{ - MultiOptionData, MultiOptionQuestionOption, QuestionData, QuestionType, QuestionTypeParent, -}; +use crate::models::question::QuestionType; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use snowflake::SnowflakeIdGenerator; -use sqlx::{Pool, Postgres, Transaction}; +use sqlx::{Postgres, Transaction}; use std::ops::DerefMut; /// The `Answer` type that will be sent in API responses. @@ -64,7 +62,6 @@ pub struct AnswerTypeApplicationId { impl Answer { pub async fn create( - user_id: i64, application_id: i64, question_id: i64, answer_data: AnswerData, @@ -379,9 +376,6 @@ impl AnswerData { let options = ranking_answers.expect("Data should exist for Ranking variant"); AnswerData::Ranking(options) } - _ => { - AnswerData::ShortAnswer("".to_string()) // Should never be reached, hence return ShortAnswer - } }; } diff --git a/backend/server/src/models/app.rs b/backend/server/src/models/app.rs index 91ae5f26..5fd59143 100644 --- a/backend/server/src/models/app.rs +++ b/backend/server/src/models/app.rs @@ -2,6 +2,8 @@ use crate::handler::answer::AnswerHandler; use crate::handler::application::ApplicationHandler; use crate::handler::auth::google_callback; use crate::handler::campaign::CampaignHandler; +use crate::handler::email_template::EmailTemplateHandler; +use crate::handler::offer::OfferHandler; use crate::handler::organisation::OrganisationHandler; use crate::handler::question::QuestionHandler; use crate::handler::rating::RatingHandler; @@ -18,9 +20,6 @@ use snowflake::SnowflakeIdGenerator; use sqlx::postgres::PgPoolOptions; use sqlx::{Pool, Postgres}; use std::env; -use crate::handler::email_template::EmailTemplateHandler; -use crate::handler::offer::OfferHandler; -use crate::models::organisation::Organisation; #[derive(Clone)] pub struct AppState { @@ -92,20 +91,25 @@ pub async fn app() -> Result { get(ApplicationHandler::get_from_curr_user), ) .route("/api/v1/organisation", post(OrganisationHandler::create)) - .route("/api/v1/organisation/slug_check", post(OrganisationHandler::check_organisation_slug_availability)) + .route( + "/api/v1/organisation/slug_check", + post(OrganisationHandler::check_organisation_slug_availability), + ) .route( "/api/v1/organisation/:organisation_id", get(OrganisationHandler::get).delete(OrganisationHandler::delete), ) - .route("/api/v1/organisation/slug/:slug", - get(OrganisationHandler::get_by_slug)) + .route( + "/api/v1/organisation/slug/:slug", + get(OrganisationHandler::get_by_slug), + ) .route( "/api/v1/organisation/:organisation_id/campaign", post(OrganisationHandler::create_campaign), ) .route( "/api/v1/organisation/:organisation_id/campaign/slug_check", - post(OrganisationHandler::check_campaign_slug_availability) + post(OrganisationHandler::check_campaign_slug_availability), ) .route( "/api/v1/organisation/:organisation_id/campaigns", @@ -113,11 +117,11 @@ pub async fn app() -> Result { ) .route( "/api/v1/organisation/:organisation_id/email_template", - post(OrganisationHandler::create_email_template) + post(OrganisationHandler::create_email_template), ) .route( "/api/v1/organisation/:organisation_id/email_templates", - get(OrganisationHandler::get_all_email_templates) + get(OrganisationHandler::get_all_email_templates), ) .route( "/api/v1/organisation/:organisation_id/logo", @@ -183,7 +187,7 @@ pub async fn app() -> Result { ) .route( "/api/v1/campaign/slug/:organisation_slug/:campaign_slug", - get(CampaignHandler::get_by_slugs) + get(CampaignHandler::get_by_slugs), ) .route("/api/v1/campaign", get(CampaignHandler::get_all)) .route( @@ -208,11 +212,11 @@ pub async fn app() -> Result { ) .route( "/api/v1/campaign/:campaign_id/offer", - post(CampaignHandler::create_offer) + post(CampaignHandler::create_offer), ) .route( "/api/v1/campaign/:campaign_id/offers", - get(CampaignHandler::get_offers) + get(CampaignHandler::get_offers), ) .route( "/api/v1/application/:application_id", @@ -231,7 +235,7 @@ pub async fn app() -> Result { get(AnswerHandler::get_all_common_by_application), ) .route( - "/api/v1/application/:applicaiton_id/answer", + "/api/v1/application/:application_id/answer", post(AnswerHandler::create), ) .route( @@ -246,19 +250,21 @@ pub async fn app() -> Result { "/api/v1/email_template/:template_id", get(EmailTemplateHandler::get) .patch(EmailTemplateHandler::update) - .delete(EmailTemplateHandler::delete) + .delete(EmailTemplateHandler::delete), ) .route( "/api/v1/offer/:offer_id", - get(OfferHandler::get).delete(OfferHandler::delete).post(OfferHandler::reply) + get(OfferHandler::get) + .delete(OfferHandler::delete) + .post(OfferHandler::reply), ) .route( "/api/v1/offer/:offer_id/preview", - get(OfferHandler::preview_email) + get(OfferHandler::preview_email), ) .route( "/api/v1/offer/:offer_id/send", - post(OfferHandler::send_offer) + post(OfferHandler::send_offer), ) .with_state(state)) } diff --git a/backend/server/src/models/auth.rs b/backend/server/src/models/auth.rs index 06be344c..38902d2b 100644 --- a/backend/server/src/models/auth.rs +++ b/backend/server/src/models/auth.rs @@ -4,7 +4,8 @@ use crate::service::answer::user_is_answer_owner; use crate::service::application::{user_is_application_admin, user_is_application_owner}; use crate::service::auth::{assert_is_super_user, extract_user_id_from_request}; use crate::service::campaign::user_is_campaign_admin; -use crate::service::jwt::decode_auth_token; +use crate::service::email_template::user_is_email_template_admin; +use crate::service::offer::{assert_user_is_offer_admin, assert_user_is_offer_recipient}; use crate::service::organisation::assert_user_is_organisation_admin; use crate::service::question::user_is_question_admin; use crate::service::rating::{ @@ -16,12 +17,8 @@ use axum::extract::{FromRef, FromRequestParts, Path}; use axum::http::request::Parts; use axum::response::{IntoResponse, Redirect, Response}; use axum::{async_trait, RequestPartsExt}; -use axum_extra::{headers::Cookie, TypedHeader}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use crate::models::offer::Offer; -use crate::service::email_template::user_is_email_template_admin; -use crate::service::offer::{assert_user_is_offer_admin, assert_user_is_offer_recipient}; // tells the web framework how to take the url query params they will have #[derive(Deserialize, Serialize)] @@ -63,9 +60,7 @@ where let app_state = AppState::from_ref(state); let user_id = extract_user_id_from_request(parts, &app_state).await?; - Ok(AuthUser { - user_id, - }) + Ok(AuthUser { user_id }) } } @@ -88,9 +83,7 @@ where assert_is_super_user(user_id, &app_state.db).await?; - Ok(SuperUser { - user_id, - }) + Ok(SuperUser { user_id }) } } @@ -293,7 +286,8 @@ where .await .map_err(|_| ChaosError::BadRequest)?; - assert_user_is_application_reviewer_given_rating_id(user_id, rating_id, &app_state.db).await?; + assert_user_is_application_reviewer_given_rating_id(user_id, rating_id, &app_state.db) + .await?; Ok(ApplicationReviewerGivenRatingId { user_id }) } @@ -320,7 +314,8 @@ where .await .map_err(|_| ChaosError::BadRequest)?; - assert_user_is_rating_creator_and_organisation_member(user_id, rating_id, &app_state.db).await?; + assert_user_is_rating_creator_and_organisation_member(user_id, rating_id, &app_state.db) + .await?; Ok(RatingCreator { user_id }) } @@ -340,7 +335,7 @@ where async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { let app_state = AppState::from_ref(state); - let user_id= extract_user_id_from_request(parts, &app_state).await?; + let user_id = extract_user_id_from_request(parts, &app_state).await?; let question_id = *parts .extract::>>() @@ -427,7 +422,7 @@ where async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { let app_state = AppState::from_ref(state); - let user_id= extract_user_id_from_request(parts, &app_state).await?; + let user_id = extract_user_id_from_request(parts, &app_state).await?; let template_id = *parts .extract::>>() @@ -456,7 +451,7 @@ where async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { let app_state = AppState::from_ref(state); - let user_id= extract_user_id_from_request(parts, &app_state).await?; + let user_id = extract_user_id_from_request(parts, &app_state).await?; let offer_id = *parts .extract::>>() @@ -485,7 +480,7 @@ where async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { let app_state = AppState::from_ref(state); - let user_id= extract_user_id_from_request(parts, &app_state).await?; + let user_id = extract_user_id_from_request(parts, &app_state).await?; let offer_id = *parts .extract::>>() diff --git a/backend/server/src/models/campaign.rs b/backend/server/src/models/campaign.rs index 3d350641..5897dc27 100644 --- a/backend/server/src/models/campaign.rs +++ b/backend/server/src/models/campaign.rs @@ -1,9 +1,9 @@ -use std::ops::DerefMut; use chrono::{DateTime, Utc}; use s3::Bucket; use serde::{Deserialize, Serialize}; use sqlx::{FromRow, Transaction}; use sqlx::{Pool, Postgres}; +use std::ops::DerefMut; use uuid::Uuid; use super::{error::ChaosError, storage::Storage}; @@ -63,7 +63,9 @@ pub struct CampaignBannerUpdate { impl Campaign { /// Get a list of all campaigns, both published and unpublished - pub async fn get_all(transaction: &mut Transaction<'_, Postgres>) -> Result, ChaosError> { + pub async fn get_all( + transaction: &mut Transaction<'_, Postgres>, + ) -> Result, ChaosError> { let campaigns = sqlx::query_as!( Campaign, " @@ -78,7 +80,10 @@ impl Campaign { } /// Get a campaign based on it's id - pub async fn get(id: i64, transaction: &mut Transaction<'_, Postgres>) -> Result { + pub async fn get( + id: i64, + transaction: &mut Transaction<'_, Postgres>, + ) -> Result { let campaign = sqlx::query_as!( CampaignDetails, " @@ -97,7 +102,11 @@ impl Campaign { Ok(campaign) } - pub async fn check_slug_availability(organisation_id: i64, slug: String, pool: &Pool) -> Result<(), ChaosError> { + pub async fn check_slug_availability( + organisation_id: i64, + slug: String, + pool: &Pool, + ) -> Result<(), ChaosError> { if !slug.is_ascii() { return Err(ChaosError::BadRequest); } @@ -109,19 +118,23 @@ impl Campaign { organisation_id, slug ) - .fetch_one(pool) - .await? - .exists - .expect("`exists` should always exist in this query result"); + .fetch_one(pool) + .await? + .exists + .expect("`exists` should always exist in this query result"); if exists { - return Err(ChaosError::BadRequest) + return Err(ChaosError::BadRequest); } Ok(()) } - pub async fn get_by_slugs(organisation_slug: String, campaign_slug: String, transaction: &mut Transaction<'_, Postgres>) -> Result { + pub async fn get_by_slugs( + organisation_slug: String, + campaign_slug: String, + transaction: &mut Transaction<'_, Postgres>, + ) -> Result { let campaign = sqlx::query_as!( CampaignDetails, " @@ -135,8 +148,8 @@ impl Campaign { campaign_slug, organisation_slug ) - .fetch_one(transaction.deref_mut()) - .await?; + .fetch_one(transaction.deref_mut()) + .await?; Ok(campaign) } @@ -197,7 +210,10 @@ impl Campaign { } /// Delete a campaign from the database - pub async fn delete(id: i64, transaction: &mut Transaction<'_, Postgres>) -> Result<(), ChaosError> { + pub async fn delete( + id: i64, + transaction: &mut Transaction<'_, Postgres>, + ) -> Result<(), ChaosError> { _ = sqlx::query!( " DELETE FROM campaigns WHERE id = $1 RETURNING id diff --git a/backend/server/src/models/email_template.rs b/backend/server/src/models/email_template.rs index cd84130a..dd91cf00 100644 --- a/backend/server/src/models/email_template.rs +++ b/backend/server/src/models/email_template.rs @@ -1,13 +1,10 @@ -use std::collections::HashMap; -use std::ops::DerefMut; +use crate::models::error::ChaosError; use chrono::{DateTime, Local, Utc}; use handlebars::Handlebars; use serde::{Deserialize, Serialize}; -use snowflake::SnowflakeIdGenerator; use sqlx::{Pool, Postgres, Transaction}; -use crate::models::application::Application; -use crate::models::campaign::Campaign; -use crate::models::error::ChaosError; +use std::collections::HashMap; +use std::ops::DerefMut; /// Email templates to update applicants /// Supported tags: @@ -25,45 +22,60 @@ pub struct EmailTemplate { } impl EmailTemplate { - pub async fn get(id: i64, transaction: &mut Transaction<'_, Postgres>) -> Result { + pub async fn get( + id: i64, + transaction: &mut Transaction<'_, Postgres>, + ) -> Result { let template = sqlx::query_as!( EmailTemplate, "SELECT * FROM email_templates WHERE id = $1", id ) - .fetch_one(transaction.deref_mut()).await?; + .fetch_one(transaction.deref_mut()) + .await?; Ok(template) } - pub async fn get_all_by_organisation(organisation_id: i64, pool: &Pool) -> Result, ChaosError> { + pub async fn get_all_by_organisation( + organisation_id: i64, + pool: &Pool, + ) -> Result, ChaosError> { let templates = sqlx::query_as!( EmailTemplate, "SELECT * FROM email_templates WHERE organisation_id = $1", organisation_id ) - .fetch_all(pool).await?; + .fetch_all(pool) + .await?; Ok(templates) } - pub async fn update(id: i64, name: String, template: String, pool: &Pool) -> Result<(), ChaosError> { + pub async fn update( + id: i64, + name: String, + template: String, + pool: &Pool, + ) -> Result<(), ChaosError> { let _ = sqlx::query!( " UPDATE email_templates SET name = $2, template = $3 WHERE id = $1 RETURNING id ", - id, name, template + id, + name, + template ) - .fetch_one(pool).await?; + .fetch_one(pool) + .await?; Ok(()) } pub async fn delete(id: i64, pool: &Pool) -> Result<(), ChaosError> { - let _ = sqlx::query!( - "DELETE FROM email_templates WHERE id = $1 RETURNING id", - id - ).fetch_one(pool).await?; + let _ = sqlx::query!("DELETE FROM email_templates WHERE id = $1 RETURNING id", id) + .fetch_one(pool) + .await?; Ok(()) } @@ -87,10 +99,16 @@ impl EmailTemplate { data.insert("role", role); data.insert("organisation_name", organisation_name); data.insert("campaign_name", campaign_name); - data.insert("expiry_date", expiry_date.with_timezone(&Local).format("%d/%m/%Y %H:%M").to_string()); + data.insert( + "expiry_date", + expiry_date + .with_timezone(&Local) + .format("%d/%m/%Y %H:%M") + .to_string(), + ); let final_string = handlebars.render("template", &data)?; Ok(final_string) } -} \ No newline at end of file +} diff --git a/backend/server/src/models/mod.rs b/backend/server/src/models/mod.rs index c8f5f02e..e2d03ca5 100644 --- a/backend/server/src/models/mod.rs +++ b/backend/server/src/models/mod.rs @@ -3,7 +3,9 @@ pub mod app; pub mod application; pub mod auth; pub mod campaign; +pub mod email_template; pub mod error; +pub mod offer; pub mod organisation; pub mod question; pub mod rating; @@ -11,5 +13,3 @@ pub mod role; pub mod storage; pub mod transaction; pub mod user; -pub mod email_template; -pub mod offer; diff --git a/backend/server/src/models/offer.rs b/backend/server/src/models/offer.rs index 52725601..d0201756 100644 --- a/backend/server/src/models/offer.rs +++ b/backend/server/src/models/offer.rs @@ -1,12 +1,10 @@ -use std::ops::DerefMut; +use crate::models::email_template::EmailTemplate; +use crate::models::error::ChaosError; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use snowflake::SnowflakeIdGenerator; use sqlx::{Postgres, Transaction}; -use crate::models::application::Application; -use crate::models::campaign::Campaign; -use crate::models::email_template::EmailTemplate; -use crate::models::error::ChaosError; +use std::ops::DerefMut; #[derive(Deserialize)] pub struct Offer { @@ -44,7 +42,7 @@ pub enum OfferStatus { Draft, Sent, Accepted, - Declined + Declined, } #[derive(Deserialize)] @@ -53,7 +51,15 @@ pub struct OfferReply { } impl Offer { - pub async fn create(campaign_id: i64, application_id: i64, email_template_id: i64, role_id: i64, expiry: DateTime, transaction: &mut Transaction<'_, Postgres>, mut snowflake_id_generator: SnowflakeIdGenerator) -> Result { + pub async fn create( + campaign_id: i64, + application_id: i64, + email_template_id: i64, + role_id: i64, + expiry: DateTime, + transaction: &mut Transaction<'_, Postgres>, + mut snowflake_id_generator: SnowflakeIdGenerator, + ) -> Result { let id = snowflake_id_generator.real_time_generate(); let _ = sqlx::query!( @@ -73,7 +79,10 @@ impl Offer { Ok(id) } - pub async fn get(id: i64, transaction: &mut Transaction<'_, Postgres>) -> Result { + pub async fn get( + id: i64, + transaction: &mut Transaction<'_, Postgres>, + ) -> Result { let offer = sqlx::query_as!( OfferDetails, r#" @@ -97,13 +106,16 @@ impl Offer { "#, id ) - .fetch_one(transaction.deref_mut()) - .await?; + .fetch_one(transaction.deref_mut()) + .await?; Ok(offer) } - pub async fn get_by_campaign(campaign_id: i64, transaction: &mut Transaction<'_, Postgres>) -> Result, ChaosError> { + pub async fn get_by_campaign( + campaign_id: i64, + transaction: &mut Transaction<'_, Postgres>, + ) -> Result, ChaosError> { let offers = sqlx::query_as!( OfferDetails, r#" @@ -127,13 +139,16 @@ impl Offer { "#, campaign_id ) - .fetch_all(transaction.deref_mut()) - .await?; + .fetch_all(transaction.deref_mut()) + .await?; Ok(offers) } - pub async fn delete(id: i64, transaction: &mut Transaction<'_, Postgres>) -> Result<(), ChaosError> { + pub async fn delete( + id: i64, + transaction: &mut Transaction<'_, Postgres>, + ) -> Result<(), ChaosError> { let _ = sqlx::query!("DELETE FROM offers WHERE id = $1 RETURNING id", id) .fetch_one(transaction.deref_mut()) .await?; @@ -141,11 +156,15 @@ impl Offer { Ok(()) } - pub async fn reply(id: i64, accept: bool, transaction: &mut Transaction<'_, Postgres>) -> Result<(), ChaosError> { + pub async fn reply( + id: i64, + accept: bool, + transaction: &mut Transaction<'_, Postgres>, + ) -> Result<(), ChaosError> { let offer = Offer::get(id, transaction).await?; if Utc::now() > offer.expiry { - return Err(ChaosError::BadRequest) + return Err(ChaosError::BadRequest); } let mut status = OfferStatus::Accepted; @@ -153,24 +172,52 @@ impl Offer { status = OfferStatus::Declined; } - let _ = sqlx::query!("UPDATE offers SET status = $2 WHERE id = $1", id, status as OfferStatus) - .execute(transaction.deref_mut()) - .await?; + let _ = sqlx::query!( + "UPDATE offers SET status = $2 WHERE id = $1", + id, + status as OfferStatus + ) + .execute(transaction.deref_mut()) + .await?; Ok(()) } - pub async fn preview_email(id: i64, transaction: &mut Transaction<'_, Postgres>) -> Result { + pub async fn preview_email( + id: i64, + transaction: &mut Transaction<'_, Postgres>, + ) -> Result { let offer = Offer::get(id, transaction).await?; - let email = EmailTemplate::generate_email(offer.user_name, offer.role_name, offer.organisation_name, offer.campaign_name, offer.expiry, offer.email_template_id, transaction).await?; + let email = EmailTemplate::generate_email( + offer.user_name, + offer.role_name, + offer.organisation_name, + offer.campaign_name, + offer.expiry, + offer.email_template_id, + transaction, + ) + .await?; Ok(email) } - pub async fn send_offer(id: i64, transaction: &mut Transaction<'_, Postgres>) -> Result<(), ChaosError> { + pub async fn send_offer( + id: i64, + transaction: &mut Transaction<'_, Postgres>, + ) -> Result<(), ChaosError> { let offer = Offer::get(id, transaction).await?; - let email = EmailTemplate::generate_email(offer.user_name, offer.role_name, offer.organisation_name, offer.campaign_name, offer.expiry, offer.email_template_id, transaction).await?; + let email = EmailTemplate::generate_email( + offer.user_name, + offer.role_name, + offer.organisation_name, + offer.campaign_name, + offer.expiry, + offer.email_template_id, + transaction, + ) + .await?; // TODO: Send email e.g. send_email(offer.user_email, email).await?; Ok(()) } -} \ No newline at end of file +} diff --git a/backend/server/src/models/organisation.rs b/backend/server/src/models/organisation.rs index 890a02cf..94824c37 100644 --- a/backend/server/src/models/organisation.rs +++ b/backend/server/src/models/organisation.rs @@ -68,7 +68,7 @@ pub struct AdminToRemove { #[derive(Deserialize)] pub struct SlugCheck { - pub slug: String + pub slug: String, } impl Organisation { @@ -112,7 +112,10 @@ impl Organisation { Ok(()) } - pub async fn check_slug_availability(slug: String, pool: &Pool) -> Result<(), ChaosError> { + pub async fn check_slug_availability( + slug: String, + pool: &Pool, + ) -> Result<(), ChaosError> { if !slug.is_ascii() { return Err(ChaosError::BadRequest); } @@ -123,13 +126,13 @@ impl Organisation { ", slug ) - .fetch_one(pool) - .await? - .exists - .expect("`exists` should always exist in this query result"); + .fetch_one(pool) + .await? + .exists + .expect("`exists` should always exist in this query result"); if exists { - return Err(ChaosError::BadRequest) + return Err(ChaosError::BadRequest); } Ok(()) @@ -151,7 +154,10 @@ impl Organisation { Ok(organisation) } - pub async fn get_by_slug(slug: String, pool: &Pool) -> Result { + pub async fn get_by_slug( + slug: String, + pool: &Pool, + ) -> Result { let organisation = sqlx::query_as!( OrganisationDetails, " @@ -161,8 +167,8 @@ impl Organisation { ", slug ) - .fetch_one(pool) - .await?; + .fetch_one(pool) + .await?; Ok(organisation) } @@ -430,7 +436,13 @@ impl Organisation { Ok(()) } - pub async fn create_email_template(organisation_id: i64, name: String, template: String, pool: &Pool, mut snowflake_generator: SnowflakeIdGenerator,) -> Result { + pub async fn create_email_template( + organisation_id: i64, + name: String, + template: String, + pool: &Pool, + mut snowflake_generator: SnowflakeIdGenerator, + ) -> Result { let id = snowflake_generator.generate(); let _ = sqlx::query!( @@ -438,11 +450,13 @@ impl Organisation { INSERT INTO email_templates (id, organisation_id, name, template) VALUES ($1, $2, $3, $4) ", - id, organisation_id, - name, template + id, + organisation_id, + name, + template ) - .execute(pool) - .await?; + .execute(pool) + .await?; Ok(id) } diff --git a/backend/server/src/models/role.rs b/backend/server/src/models/role.rs index 43013c93..ec07082e 100644 --- a/backend/server/src/models/role.rs +++ b/backend/server/src/models/role.rs @@ -1,9 +1,9 @@ -use std::ops::DerefMut; use crate::models::error::ChaosError; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use snowflake::SnowflakeIdGenerator; use sqlx::{FromRow, Pool, Postgres, Transaction}; +use std::ops::DerefMut; #[derive(Deserialize, Serialize, Clone, FromRow, Debug)] pub struct Role { diff --git a/backend/server/src/service/auth.rs b/backend/server/src/service/auth.rs index 4d4ee154..68b9302f 100644 --- a/backend/server/src/service/auth.rs +++ b/backend/server/src/service/auth.rs @@ -1,14 +1,13 @@ -use axum::extract::FromRef; +use crate::models::app::AppState; +use crate::models::error::ChaosError; +use crate::models::user::UserRole; +use crate::service::jwt::decode_auth_token; use axum::http::request::Parts; use axum::RequestPartsExt; use axum_extra::headers::Cookie; use axum_extra::TypedHeader; -use crate::models::user::UserRole; use snowflake::SnowflakeIdGenerator; use sqlx::{Pool, Postgres}; -use crate::models::app::AppState; -use crate::models::error::ChaosError; -use crate::service::jwt::decode_auth_token; /// Checks if a user exists in DB based on given email address. If so, their user_id is returned. /// Otherwise, a new user is created in the DB, and the new id is returned. @@ -53,7 +52,9 @@ pub async fn assert_is_super_user(user_id: i64, pool: &Pool) -> Result UserRole::SuperUser as UserRole ) .fetch_one(pool) - .await?.exists.expect("`exists` should always exist in this query result"); + .await? + .exists + .expect("`exists` should always exist in this query result"); if !is_super_user { return Err(ChaosError::Unauthorized); @@ -62,7 +63,10 @@ pub async fn assert_is_super_user(user_id: i64, pool: &Pool) -> Result Ok(()) } -pub async fn extract_user_id_from_request(parts: &mut Parts, state: &AppState) -> Result { +pub async fn extract_user_id_from_request( + parts: &mut Parts, + state: &AppState, +) -> Result { let decoding_key = &state.decoding_key; let jwt_validator = &state.jwt_validator; let TypedHeader(cookies) = parts @@ -76,4 +80,4 @@ pub async fn extract_user_id_from_request(parts: &mut Parts, state: &AppState) - decode_auth_token(token, decoding_key, jwt_validator).ok_or(ChaosError::NotLoggedIn)?; Ok(claims.sub) -} \ No newline at end of file +} diff --git a/backend/server/src/service/email_template.rs b/backend/server/src/service/email_template.rs index c6ad5d31..3ef19e2a 100644 --- a/backend/server/src/service/email_template.rs +++ b/backend/server/src/service/email_template.rs @@ -17,10 +17,10 @@ pub async fn user_is_email_template_admin( template_id, user_id ) - .fetch_one(pool) - .await? - .exists - .expect("`exists` should always exist in this query result"); + .fetch_one(pool) + .await? + .exists + .expect("`exists` should always exist in this query result"); if !is_admin { return Err(ChaosError::Unauthorized); diff --git a/backend/server/src/service/mod.rs b/backend/server/src/service/mod.rs index 53f76596..e4ad6769 100644 --- a/backend/server/src/service/mod.rs +++ b/backend/server/src/service/mod.rs @@ -2,11 +2,11 @@ pub mod answer; pub mod application; pub mod auth; pub mod campaign; +pub mod email_template; pub mod jwt; pub mod oauth2; +pub mod offer; pub mod organisation; pub mod question; pub mod rating; pub mod role; -pub mod email_template; -pub mod offer; \ No newline at end of file diff --git a/backend/server/src/service/offer.rs b/backend/server/src/service/offer.rs index 1a639441..f044ea7b 100644 --- a/backend/server/src/service/offer.rs +++ b/backend/server/src/service/offer.rs @@ -1,6 +1,6 @@ use crate::models::error::ChaosError; -use sqlx::{Pool, Postgres}; use crate::models::offer::Offer; +use sqlx::{Pool, Postgres}; pub async fn assert_user_is_offer_admin( user_id: i64, @@ -19,10 +19,10 @@ pub async fn assert_user_is_offer_admin( offer_id, user_id ) - .fetch_one(pool) - .await? - .exists - .expect("`exists` should always exist in this query result"); + .fetch_one(pool) + .await? + .exists + .expect("`exists` should always exist in this query result"); if !is_admin { return Err(ChaosError::Unauthorized); @@ -40,8 +40,8 @@ pub async fn assert_user_is_offer_recipient( let offer = Offer::get(offer_id, tx).await?; if offer.user_id != user_id { - return Err(ChaosError::Unauthorized) + return Err(ChaosError::Unauthorized); } Ok(()) -} \ No newline at end of file +}