Skip to content

Commit

Permalink
tests passing: email working
Browse files Browse the repository at this point in the history
  • Loading branch information
FayCarsons committed Jun 21, 2024
1 parent ce3831f commit d1ab22e
Show file tree
Hide file tree
Showing 12 changed files with 308 additions and 36 deletions.
54 changes: 40 additions & 14 deletions backend/src/api/order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ use model::{

use std::{collections::HashMap, sync::Arc};

use crate::{DbConn, DbPool};
use crate::{
mail::{self, shipped},
DbConn, DbPool, Mailer,
};

use super::stripe;

Expand Down Expand Up @@ -46,6 +49,7 @@ pub async fn get_orders(
#[put("/orders/shipped")]
pub async fn order_shipped(
pool: web::Data<DbPool>,
mailer: web::Data<Mailer>,
order_id: web::Path<u32>,
tracking_number: web::Bytes,
) -> Result<HttpResponse> {
Expand All @@ -55,25 +59,46 @@ pub async fn order_shipped(
.map_err(|e| error::ErrorInternalServerError(format!("Cannot connect to DB: {e}")))?;

let id = order_id.into_inner() as i32;
web::block(move || {
match diesel::update(orders::table)
let order = web::block(move || {
let orders = orders::table
.select(order::TableOrder::as_select())
.filter(orders::id.eq(id))
.filter(orders::shipped.eq(false))
.set((
orders::shipped.eq(true),
orders::tracking_number.eq(String::from_iter(
tracking_number.into_iter().map(char::from),
)),
))
.execute(&mut conn)
{
Ok(0usize) => Err("Error: Order has already been shipped/ID is invalid"),
_ => Ok(()),
.get_results::<order::TableOrder>(&mut conn)
.map_err(|e| format!("Cannot fetch order {id}: {e}"))?;

let [order, ..] = orders.as_slice() else {
return Err(format!("Order {id} not found!"));
};

if let order::TableOrder { shipped: false, .. } = &order {
match diesel::update(orders::table)
.filter(orders::id.eq(id))
.set((
orders::shipped.eq(true),
orders::tracking_number.eq(String::from_iter(
tracking_number.into_iter().map(char::from),
)),
))
.execute(&mut conn)
{
Ok(0usize) => {
Err("Error: Order has already been shipped/ID is invalid".to_string())
}
_ => Ok(order.clone()),
}
} else {
Err(format!("Order {id} has already been marked as shipped!"))
}
})
.await?
.map_err(error::ErrorBadRequest)?;

let shipping = shipped::Shipped::try_from(order).map_err(error::ErrorBadRequest)?;

mail::send::send_tracking(shipping, mailer.into_inner())
.await
.map_err(error::ErrorInternalServerError)?;

Ok(HttpResponse::Ok().finish())
}

Expand Down Expand Up @@ -107,6 +132,7 @@ pub async fn insert_order(
name: &name,
total: total as i32,
email: &email,
shipped: false,
};

let order_id = diesel::insert_into(orders::table)
Expand Down
File renamed without changes.
3 changes: 2 additions & 1 deletion backend/src/mail/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod confirmation;
pub mod send;
pub mod templates;
pub mod shipped;
30 changes: 27 additions & 3 deletions backend/src/mail/send.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
use crate::{api::stripe::UserData, Mailer};
use actix_web::Result;

use askama::Template;

use lettre::{
message::{MultiPart, SinglePart},
AsyncTransport, Message,
};

use super::templates::{Confirmation, Template};
use super::{confirmation, shipped};

use std::sync::Arc;

pub async fn send_confirmation(user: UserData, mailer: Arc<Mailer>) -> Result<(), String> {
let confirmation = Confirmation::from(&user);
let confirmation = confirmation::Confirmation::from(&user);
let html = SinglePart::html(confirmation.render().unwrap());
let plaintext = SinglePart::plain(confirmation.render_plaintext());

let email = Message::builder()
.from("Kiggyshop <[email protected]>".parse().unwrap())
.to("Fay <[email protected]>".parse().unwrap())
.to(user.email.parse().unwrap())
.subject("Thank you for your order!")
.multipart(
MultiPart::alternative()
Expand All @@ -30,3 +33,24 @@ pub async fn send_confirmation(user: UserData, mailer: Arc<Mailer>) -> Result<()
Err(e) => Err(e.to_string()),
}
}

pub async fn send_tracking(shipping: shipped::Shipped, mailer: Arc<Mailer>) -> Result<(), String> {
let html = SinglePart::html(shipping.render().unwrap());
let plaintext = SinglePart::plain(shipping.render_plaintext());

let email = Message::builder()
.from("KiggyShop <[email protected]>".parse().unwrap())
.to(shipping.email.parse().unwrap())
.subject("Your orderhas shipped!")
.multipart(
MultiPart::alternative()
.singlepart(html)
.singlepart(plaintext),
)
.unwrap();

match mailer.send(email).await {
Ok(_) => Ok(()),
Err(e) => Err(e.to_string()),
}
}
86 changes: 86 additions & 0 deletions backend/src/mail/shipped.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use model::order;

#[derive(Debug, Clone, askama::Template)]
#[template(path = "shipped.html")]
pub struct Shipped {
id: u32,
name: String,
pub email: String,
tracking: String,
}

impl TryFrom<order::Order> for Shipped {
type Error = String;

fn try_from(
order::Order {
id,
name,
email,
tracking_number,
..
}: order::Order,
) -> Result<Self, Self::Error> {
if let Some(tracking) = tracking_number {
Ok(Self {
id,
name,
email,
tracking,
})
} else {
Err(format!("No tracking # on order {id}"))
}
}
}

impl TryFrom<order::TableOrder> for Shipped {
type Error = String;

fn try_from(
order::TableOrder {
id,
name,
email,
tracking_number,
..
}: order::TableOrder,
) -> Result<Self, Self::Error> {
if let Some(tracking) = tracking_number {
Ok(Self {
id: id as u32,
name,
email,
tracking,
})
} else {
Err(format!("Order {id} does not contain a tracking number!"))
}
}
}

impl Shipped {
pub fn new<T, U>(id: T, email: U, name: U, tracking: U) -> Self
where
T: Into<u32>,
U: std::fmt::Display,
{
let id = id.into();
let name = name.to_string();
let email = email.to_string();
let tracking = tracking.to_string();
Self {
id,
name,
email,
tracking,
}
}

pub fn render_plaintext(&self) -> String {
format!(
r#"Hi, {}! Your order has shipped!\nOrder #: {}\nUSPS tracking #: {}"#,
self.name, self.id, self.tracking
)
}
}
2 changes: 1 addition & 1 deletion backend/src/tests/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ mod tests {
async fn test_delete_order() {
let (db, pool) = create_db_pool();

// Dummy order
let Order { name, .. } = serde_json::from_str::<Order>(include_str!("./mock_order.json"))
.expect("Cannot deserialize mock order");

Expand All @@ -68,6 +67,7 @@ mod tests {
name: &name,
total: 30_00,
email: "",
shipped: false,
}])
.execute(&mut conn)
.expect("Cannot insert mock order into DB");
Expand Down
3 changes: 3 additions & 0 deletions backend/src/tests/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ mod tests {
name: &name,
email: "",
total: 30_00,
shipped: false,
})
.returning(orders::dsl::id)
.get_result::<i32>(&mut conn);
Expand Down Expand Up @@ -81,6 +82,7 @@ mod tests {
name: &name,
email: "",
total: 30_00,
shipped: false,
})
.returning(orders::dsl::id)
.get_result::<i32>(&mut conn);
Expand Down Expand Up @@ -120,6 +122,7 @@ mod tests {
name: &name,
email: "",
total: 30_00,
shipped: false,
})
.returning(orders::dsl::id)
.get_result::<i32>(&mut conn);
Expand Down
41 changes: 40 additions & 1 deletion backend/src/tests/mail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use model::{item, order};

use crate::{
api::stripe,
mail::{self},
mail::{self, shipped},
Mailer,
};

Expand Down Expand Up @@ -46,6 +46,7 @@ async fn test_confirmation_email() {
address: Some(address),
email,
total,
subtotal: total,
cart,
};

Expand All @@ -62,3 +63,41 @@ async fn test_confirmation_email() {
panic!("Cannot send confirmation test email: {e}");
}
}

#[actix_web::test]
async fn test_shipping() {
let order = serde_json::from_slice::<order::Order>(include_bytes!("mock_order.json"))
.expect("Cannot parse mock orders");

let order::Order {
id,
name,
email,
total,
..
} = order;

let table_order = order::TableOrder {
id: id as i32,
name,
email,
total: total as i32,
shipped: false,
tracking_number: Some("URSILLY8901".to_string()),
};
let shipped =
shipped::Shipped::try_from(table_order).expect("Cannot convert <TableOrder> to <Shipped>");

let creds = Credentials::new(
dotenvy_macro::dotenv!("MAIL_USER").to_string(),
dotenvy_macro::dotenv!("MAIL_PASS").to_string(),
);
let mailer: Mailer = Mailer::relay("smtp.gmail.com")
.expect("Cannot build mailer")
.credentials(creds)
.build();

if let Err(e) = mail::send::send_tracking(shipped, Arc::new(mailer)).await {
panic!("Cannot send confirmation test email: {e}");
}
}
13 changes: 7 additions & 6 deletions backend/src/tests/mock_order.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
{
"name": "Fay Carsons",
"email": "[email protected]",
"id": 1,
"name": "foobar",
"email": "[email protected]",
"total": 23,
"cart": {
"0": 10,
"1": 20
},
"address": {
"name": "Fay",
"name": "foobar",
"number": 2323,
"street": "Thin st",
"city": "Richmond",
"state": "VA",
"street": "Large st",
"city": "Oakland",
"state": "CA",
"zipcode": 23233
},
"shipped": false
Expand Down
Loading

0 comments on commit d1ab22e

Please sign in to comment.