Skip to content

Commit 6d3f927

Browse files
authored
chore: rollback hardcoded react email template (#811)
1 parent f08c6ac commit 6d3f927

18 files changed

Lines changed: 517 additions & 554 deletions

File tree

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
use std::collections::HashSet;
2+
3+
use axum::{
4+
extract::{Json, Path, State},
5+
http::StatusCode,
6+
response::IntoResponse,
7+
};
8+
9+
use crate::models::{
10+
app::{AppMessage, AppState, AvailabilitiesMessage},
11+
auth::AuthUser,
12+
availabilities::{Availabilities, Availability},
13+
error::ChaosError,
14+
transaction::DBTransaction,
15+
};
16+
17+
pub struct AvailabilitiesHandler;
18+
19+
/// Gets or creates the user-campaign id, helper function
20+
///
21+
/// # Arguments
22+
/// * `user_id` - ID of the user for the UC pair
23+
/// * `campaign_id` - ID of the campaign for the UC pair
24+
/// * `state` - current app state
25+
/// * `transaction` - the transaction
26+
///
27+
/// # Returns
28+
/// Returns a `Result` containing either
29+
/// * `Ok(i64)` - the uc id
30+
/// * `Err(ChaosError)` - If no availabilities are found
31+
32+
async fn get_or_create_uc_id(
33+
user_id: i64,
34+
campaign_id: i64,
35+
state: &mut AppState,
36+
transaction: &mut DBTransaction<'_>,
37+
) -> Result<i64, ChaosError> {
38+
let uc_id_res =
39+
Availabilities::get_user_campaign_id(user_id, campaign_id, &mut transaction.tx).await;
40+
Ok(match uc_id_res {
41+
Err(_) => {
42+
Availabilities::create_user_campaign_availability(
43+
user_id,
44+
campaign_id,
45+
&mut state.snowflake_generator,
46+
&mut transaction.tx,
47+
)
48+
.await?
49+
}
50+
Ok(uc) => uc.id,
51+
})
52+
}
53+
54+
impl AvailabilitiesHandler {
55+
// TODO: change auth_user to an availabilities specific extractor
56+
57+
/// Retrieves all availability slots for a given user_id and campaign_id
58+
///
59+
/// # Arguments
60+
///
61+
/// * `user_id` - ID of the interviewer
62+
/// * `campaign_id` - ID of the campaign in which the user will be interviewing
63+
/// * `state` - The application state
64+
/// * `_auth_user` - The authenticated user
65+
/// * `transaction` - Database transaction
66+
///
67+
/// # Returns
68+
/// Returns a `Result` containing either
69+
/// * `Ok(Vec<Availability>)` - Vec of availabilities
70+
/// * `Err(ChaosError)` - If no availabilities are found
71+
72+
pub async fn get(
73+
Path((user_id, campaign_id)): Path<(i64, i64)>,
74+
State(mut state): State<AppState>,
75+
_auth_user: AuthUser,
76+
mut transaction: DBTransaction<'_>,
77+
) -> Result<impl IntoResponse, ChaosError> {
78+
let uc_id = get_or_create_uc_id(user_id, campaign_id, &mut state, &mut transaction).await?;
79+
80+
let res = Availabilities::get_availability_slots(uc_id, &mut transaction.tx).await?;
81+
82+
transaction.tx.commit().await?;
83+
84+
Ok((
85+
StatusCode::OK,
86+
Json(AvailabilitiesMessage {
87+
availabilities: res,
88+
}),
89+
))
90+
}
91+
92+
// TODO: change auth_user to an availabilities specific extractor
93+
94+
/// Modifies availabilities for a given user in a campaign
95+
///
96+
/// # Arguments
97+
///
98+
/// * `user_id` - ID of the interviewer
99+
/// * `campaign_id` - ID of the campaign in which the user will be interviewing
100+
/// * `state` - The application state
101+
/// * `availabilities` - the set of ALL availabilities the user now has
102+
/// * `_auth_user` - The authenticated user
103+
/// * `transaction` - Database transaction
104+
///
105+
/// # Returns
106+
/// Returns a `Result` containing either
107+
/// * `Ok(())` - If successful
108+
/// * `Err(ChaosError)` - Otherwise
109+
110+
pub async fn update(
111+
Path((user_id, campaign_id)): Path<(i64, i64)>,
112+
State(mut state): State<AppState>,
113+
_auth_user: AuthUser,
114+
mut transaction: DBTransaction<'_>,
115+
Json(availabilities): Json<Vec<Availability>>,
116+
) -> Result<impl IntoResponse, ChaosError> {
117+
let uc_id = get_or_create_uc_id(user_id, campaign_id, &mut state, &mut transaction).await?;
118+
119+
let curr_availabilities =
120+
Availabilities::get_availability_slots(uc_id, &mut transaction.tx).await?;
121+
122+
// Diff is determined by assuming all current timeslots are to be deleted and none are to be added
123+
// Since if any availability slot is passed in, then it will either be added (if not already in the db)
124+
// or it won't be removed (if it is already in the db)
125+
let mut to_delete: HashSet<Availability> = curr_availabilities.into_iter().collect();
126+
let mut to_add: Vec<Availability> = Vec::new();
127+
128+
availabilities.into_iter().for_each(|a| {
129+
if !to_delete.contains(&a) {
130+
to_add.push(a);
131+
} else {
132+
to_delete.remove(&a);
133+
}
134+
});
135+
136+
Availabilities::delete_availabilities(
137+
user_id,
138+
campaign_id,
139+
to_delete.into_iter().collect(),
140+
&mut transaction.tx,
141+
)
142+
.await?;
143+
144+
Availabilities::create_availability_slots(uc_id, to_add, &mut transaction.tx).await?;
145+
146+
transaction.tx.commit().await?;
147+
Ok(AppMessage::OkMessage("Successfully updated availabilities"))
148+
}
149+
}

backend/server/src/handler/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
pub mod answer;
2121
pub mod application;
2222
pub mod auth;
23+
pub mod availabilities;
2324
pub mod campaign;
2425
pub mod comment;
2526
pub mod email_template;

backend/server/src/handler/offer.rs

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,8 @@ impl OfferHandler {
191191

192192
let count = body.emails.len();
193193
for item in body.emails {
194-
// Create an offer record for acceptances (audit / accept-decline flow).
195194
if matches!(item.email_type, EmailType::Accept) {
196-
Offer::create(
195+
let offer_id = Offer::create(
197196
campaign_id,
198197
item.application_id,
199198
item.email_template_id,
@@ -203,23 +202,31 @@ impl OfferHandler {
203202
&mut state.snowflake_generator,
204203
)
205204
.await?;
206-
}
207-
208-
// Queue the frontend-rendered subject/body for all outcome types.
209-
// Accept emails are HTML (React Email); rejects remain plaintext for now.
210-
if state.is_dev_env {
211-
let email = item.email;
212-
let email_type = format!("{:?}", item.email_type);
213-
println!("Queuing {email_type} email to {email}");
205+
if state.is_dev_env {
206+
let email = item.email;
207+
println!("need to call offers here, but sent to: {email}");
208+
} else {
209+
Offer::send_offer(
210+
offer_id,
211+
&mut transaction.tx,
212+
state.email_credentials.clone(),
213+
)
214+
.await?;
215+
}
214216
} else {
215-
EmailQueue::add_to_queue(
216-
Some(item.name),
217-
item.email,
218-
item.subject,
219-
item.body,
220-
&mut transaction.tx,
221-
)
222-
.await?;
217+
if state.is_dev_env {
218+
let email = item.email;
219+
println!("Sending reject email to {email}");
220+
} else {
221+
EmailQueue::add_to_queue(
222+
Some(item.name),
223+
item.email,
224+
item.subject,
225+
item.body,
226+
&mut transaction.tx,
227+
)
228+
.await?;
229+
}
223230
}
224231
}
225232

backend/server/src/models/app.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::handler::answer::AnswerHandler;
22
use crate::handler::application::ApplicationHandler;
33
use crate::handler::auth::{google_auth_init, google_callback, logout, DevLoginHandler};
4+
use crate::handler::availabilities::AvailabilitiesHandler;
45
use crate::handler::campaign::CampaignHandler;
56
use crate::handler::comment::CommentHandler;
67
use crate::handler::email_template::EmailTemplateHandler;
@@ -12,6 +13,7 @@ use crate::handler::rating::RatingHandler;
1213
use crate::handler::role::RoleHandler;
1314
use crate::handler::role_status::RoleStatusHandler;
1415
use crate::handler::user::UserHandler;
16+
use crate::models::availabilities::Availability;
1517
use crate::models::email::{ChaosEmail, EmailCredentials};
1618
use crate::models::error::ChaosError;
1719
use crate::models::storage::Storage;
@@ -178,6 +180,11 @@ pub async fn init_app_state() -> AppState {
178180
}
179181
}
180182

183+
#[derive(Serialize)]
184+
pub struct AvailabilitiesMessage {
185+
pub availabilities: Vec<Availability>,
186+
}
187+
181188
pub async fn app() -> Result<(Router, AppState), ChaosError> {
182189
let state = init_app_state().await;
183190
let state_clone = state.clone();
@@ -560,6 +567,10 @@ pub async fn app() -> Result<(Router, AppState), ChaosError> {
560567
"/api/v1/invite/:code",
561568
get(InviteHandler::get).post(InviteHandler::use_invite),
562569
)
570+
.route(
571+
"/api/v1/availabilities/:user_id/:campaign_id",
572+
get(AvailabilitiesHandler::get).patch(AvailabilitiesHandler::update),
573+
)
563574
.layer(cors)
564575
.with_state(state);
565576

backend/server/src/models/email.rs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
//! the Lettre email library.
66
77
use crate::models::error::ChaosError;
8-
use lettre::message::header::ContentType;
98
use lettre::transport::smtp::authentication::Credentials;
109
use lettre::{AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor};
1110
use serde::{Deserialize, Serialize};
@@ -147,14 +146,6 @@ impl ChaosEmail {
147146
None => recipient_email_address,
148147
};
149148

150-
// Detect HTML so React Email bodies render correctly in clients.
151-
// Plaintext reject bodies still work as text/plain.
152-
let content_type = if body.trim_start().starts_with('<') {
153-
ContentType::TEXT_HTML
154-
} else {
155-
ContentType::TEXT_PLAIN
156-
};
157-
158149
let message = Message::builder()
159150
.from(
160151
format!(
@@ -166,7 +157,6 @@ impl ChaosEmail {
166157
.reply_to("chaos@devsoc.app".parse()?)
167158
.to(to.parse()?)
168159
.subject(subject)
169-
.header(content_type)
170160
.body(body)?;
171161

172162
let mailer = Self::new_connection(credentials)?;

backend/server/src/models/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ pub mod answer;
1111
pub mod app;
1212
pub mod application;
1313
pub mod auth;
14+
pub mod availabilities;
1415
pub mod campaign;
1516
pub mod comment_last_read;
1617
pub mod comment;

0 commit comments

Comments
 (0)