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

Email Templates and Offers #528

Merged
1 change: 1 addition & 0 deletions backend/migrations/20240406024211_create_organisations.sql
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
CREATE TABLE organisations (
id BIGINT PRIMARY KEY,
slug TEXT NOT NULL UNIQUE,
name TEXT NOT NULL UNIQUE,
logo UUID,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
Expand Down
4 changes: 3 additions & 1 deletion backend/migrations/20240406025537_create_campaigns.sql
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
CREATE TABLE campaigns (
id BIGINT PRIMARY KEY,
organisation_id BIGINT NOT NULL,
slug TEXT NOT NULL,
name TEXT NOT NULL,
cover_image UUID,
description TEXT,
Expand All @@ -12,7 +13,8 @@ CREATE TABLE campaigns (
FOREIGN KEY(organisation_id)
REFERENCES organisations(id)
ON DELETE CASCADE
ON UPDATE CASCADE
ON UPDATE CASCADE,
UNIQUE (organisation_id, slug)
);

CREATE TABLE campaign_roles (
Expand Down
12 changes: 12 additions & 0 deletions backend/migrations/20241124054711_email_templates.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
CREATE TABLE email_templates (
id BIGINT PRIMARY KEY,
organisation_id BIGINT NOT NULL,
name TEXT NOT NULL,
template TEXT NOT NULL,
CONSTRAINT FK_email_templates_organisations
FOREIGN KEY(organisation_id)
REFERENCES organisations(id)
ON DELETE CASCADE
ON UPDATE CASCADE,
UNIQUE (organisation_id, name)
);
32 changes: 32 additions & 0 deletions backend/migrations/20241126113027_offers.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
CREATE TYPE offer_status AS ENUM ('Draft', 'Sent', 'Accepted', 'Declined');

CREATE TABLE offers (
id BIGINT PRIMARY KEY,
campaign_id BIGINT NOT NULL,
application_id BIGINT NOT NULL,
email_template_id BIGINT NOT NULL,
role_id BIGINT NOT NULL,
expiry TIMESTAMPTZ NOT NULL,
status offer_status NOT NULL DEFAULT 'Draft',
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT FK_offers_campaigns
FOREIGN KEY(campaign_id)
REFERENCES campaigns(id)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT FK_offers_applications
FOREIGN KEY(application_id)
REFERENCES applications(id)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT FK_offers_email_templates
FOREIGN KEY(email_template_id)
REFERENCES email_templates(id)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT FK_offers_roles
FOREIGN KEY(role_id)
REFERENCES campaign_roles(id)
ON DELETE CASCADE
ON UPDATE CASCADE
);
2 changes: 1 addition & 1 deletion backend/server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ rust-s3 = "0.34.0"
rs-snowflake = "0.6"
jsonwebtoken = "9.1"
dotenvy = "0.15"

handlebars = "6.2"
10 changes: 5 additions & 5 deletions backend/server/src/handler/answer.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -13,14 +13,14 @@ pub struct AnswerHandler;
impl AnswerHandler {
pub async fn create(
State(state): State<AppState>,
Path(path): Path<i64>,
user: AuthUser,
Path(application_id): Path<i64>,
_user: ApplicationOwner,
mut transaction: DBTransaction<'_>,
Json(data): Json<NewAnswer>,
) -> Result<impl IntoResponse, ChaosError> {
// 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,
Expand Down
80 changes: 67 additions & 13 deletions backend/server/src/handler/campaign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::models::auth::AuthUser;
use crate::models::auth::CampaignAdmin;
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};
Expand All @@ -15,67 +16,87 @@ use axum::response::IntoResponse;
pub struct CampaignHandler;
impl CampaignHandler {
pub async fn get(
State(state): State<AppState>,
mut transaction: DBTransaction<'_>,
Path(id): Path<i64>,
_user: AuthUser,
) -> Result<impl IntoResponse, ChaosError> {
let campaign = Campaign::get(id, &state.db).await?;
let campaign = Campaign::get(id, &mut transaction.tx).await?;
transaction.tx.commit().await?;
Ok((StatusCode::OK, Json(campaign)))
}

pub async fn get_by_slugs(
mut transaction: DBTransaction<'_>,
Path((organisation_slug, campaign_slug)): Path<(String, String)>,
_user: AuthUser,
) -> Result<impl IntoResponse, ChaosError> {
let campaign =
Campaign::get_by_slugs(organisation_slug, campaign_slug, &mut transaction.tx).await?;
transaction.tx.commit().await?;
Ok((StatusCode::OK, Json(campaign)))
}

pub async fn get_all(
State(state): State<AppState>,
mut transaction: DBTransaction<'_>,
_user: AuthUser,
) -> Result<impl IntoResponse, ChaosError> {
let campaigns = Campaign::get_all(&state.db).await?;
let campaigns = Campaign::get_all(&mut transaction.tx).await?;
transaction.tx.commit().await?;
Ok((StatusCode::OK, Json(campaigns)))
}

pub async fn update(
State(state): State<AppState>,
mut transaction: DBTransaction<'_>,
Path(id): Path<i64>,
_admin: CampaignAdmin,
Json(request_body): Json<models::campaign::CampaignUpdate>,
) -> Result<impl IntoResponse, ChaosError> {
Campaign::update(id, request_body, &state.db).await?;
Campaign::update(id, request_body, &mut transaction.tx).await?;
transaction.tx.commit().await?;
Ok((StatusCode::OK, "Successfully updated campaign"))
}

pub async fn update_banner(
mut transaction: DBTransaction<'_>,
State(state): State<AppState>,
Path(id): Path<i64>,
_admin: CampaignAdmin,
) -> Result<impl IntoResponse, ChaosError> {
let banner_url = Campaign::update_banner(id, &state.db, &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)))
}

pub async fn delete(
State(state): State<AppState>,
mut transaction: DBTransaction<'_>,
Path(id): Path<i64>,
_admin: CampaignAdmin,
) -> Result<impl IntoResponse, ChaosError> {
Campaign::delete(id, &state.db).await?;
Campaign::delete(id, &mut transaction.tx).await?;
transaction.tx.commit().await?;
Ok((StatusCode::OK, "Successfully deleted campaign"))
}

pub async fn create_role(
mut transaction: DBTransaction<'_>,
State(state): State<AppState>,
Path(id): Path<i64>,
_admin: CampaignAdmin,
Json(data): Json<RoleUpdate>,
) -> Result<impl IntoResponse, ChaosError> {
Role::create(id, data, &state.db, state.snowflake_generator).await?;
Role::create(id, data, &mut transaction.tx, state.snowflake_generator).await?;
transaction.tx.commit().await?;
Ok((StatusCode::OK, "Successfully created role"))
}

pub async fn get_roles(
State(state): State<AppState>,
mut transaction: DBTransaction<'_>,
Path(id): Path<i64>,
_user: AuthUser,
) -> Result<impl IntoResponse, ChaosError> {
let roles = Role::get_all_in_campaign(id, &state.db).await?;

let roles = Role::get_all_in_campaign(id, &mut transaction.tx).await?;
transaction.tx.commit().await?;
Ok((StatusCode::OK, Json(roles)))
}

Expand Down Expand Up @@ -107,4 +128,37 @@ impl CampaignHandler {
transaction.tx.commit().await?;
Ok((StatusCode::OK, Json(applications)))
}

pub async fn create_offer(
Path(id): Path<i64>,
State(state): State<AppState>,
_admin: CampaignAdmin,
mut transaction: DBTransaction<'_>,
Json(data): Json<Offer>,
) -> Result<impl IntoResponse, ChaosError> {
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"))
}

pub async fn get_offers(
mut transaction: DBTransaction<'_>,
Path(id): Path<i64>,
_user: CampaignAdmin,
) -> Result<impl IntoResponse, ChaosError> {
let offers = Offer::get_by_campaign(id, &mut transaction.tx).await?;
transaction.tx.commit().await?;

Ok((StatusCode::OK, Json(offers)))
}
}
42 changes: 42 additions & 0 deletions backend/server/src/handler/email_template.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
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 {
pub async fn get(
mut transaction: DBTransaction<'_>,
Path(id): Path<i64>,
_user: EmailTemplateAdmin,
) -> Result<impl IntoResponse, ChaosError> {
let email_template = EmailTemplate::get(id, &mut transaction.tx).await?;

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

pub async fn update(
_user: EmailTemplateAdmin,
Path(id): Path<i64>,
State(state): State<AppState>,
Json(request_body): Json<EmailTemplate>,
) -> Result<impl IntoResponse, ChaosError> {
EmailTemplate::update(id, request_body.name, request_body.template, &state.db).await?;

Ok((StatusCode::OK, "Successfully updated email template"))
}

pub async fn delete(
_user: EmailTemplateAdmin,
Path(id): Path<i64>,
State(state): State<AppState>,
) -> Result<impl IntoResponse, ChaosError> {
EmailTemplate::delete(id, &state.db).await?;

Ok((StatusCode::OK, "Successfully delete email template"))
}
}
2 changes: 2 additions & 0 deletions backend/server/src/handler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ 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;
Expand Down
66 changes: 66 additions & 0 deletions backend/server/src/handler/offer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
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 {
pub async fn get(
mut transaction: DBTransaction<'_>,
Path(id): Path<i64>,
_user: OfferAdmin,
) -> Result<impl IntoResponse, ChaosError> {
let offer = Offer::get(id, &mut transaction.tx).await?;
transaction.tx.commit().await?;

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

pub async fn delete(
mut transaction: DBTransaction<'_>,
Path(id): Path<i64>,
_user: OfferAdmin,
) -> Result<impl IntoResponse, ChaosError> {
Offer::delete(id, &mut transaction.tx).await?;
transaction.tx.commit().await?;

Ok((StatusCode::OK, "Successfully deleted offer"))
}

pub async fn reply(
mut transaction: DBTransaction<'_>,
Path(id): Path<i64>,
_user: OfferRecipient,
Json(reply): Json<OfferReply>,
) -> Result<impl IntoResponse, ChaosError> {
Offer::reply(id, reply.accept, &mut transaction.tx).await?;
transaction.tx.commit().await?;

Ok((StatusCode::OK, "Successfully accepted offer"))
}

pub async fn preview_email(
mut transaction: DBTransaction<'_>,
Path(id): Path<i64>,
_user: OfferAdmin,
) -> Result<impl IntoResponse, ChaosError> {
let string = Offer::preview_email(id, &mut transaction.tx).await?;
transaction.tx.commit().await?;

Ok((StatusCode::OK, string))
}

pub async fn send_offer(
mut transaction: DBTransaction<'_>,
Path(id): Path<i64>,
_user: OfferAdmin,
) -> Result<impl IntoResponse, ChaosError> {
Offer::send_offer(id, &mut transaction.tx).await?;
transaction.tx.commit().await?;

Ok((StatusCode::OK, "Successfully sent offer"))
}
}
Loading
Loading