Skip to content

Commit

Permalink
add metric gathering
Browse files Browse the repository at this point in the history
  • Loading branch information
FayCarsons committed Jun 4, 2024
1 parent 0eb5d31 commit ba0ac20
Show file tree
Hide file tree
Showing 17 changed files with 326 additions and 67 deletions.
1 change: 1 addition & 0 deletions backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ lettre = { version = "0.11.7", default-features = false, features = [
"smtp-transport",
] }
lettre_email = "0.9.4"
chrono = { version = "0.4.38", features = ["serde"] }

[profile.test]
debug-assertions = false
78 changes: 78 additions & 0 deletions backend/src/api/metrics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// NOTE: The functions in this module ignore `Err` values because failing to log a visit is ok
use actix_web::{web, HttpRequest, Result};
use awc::Client;
use chrono::prelude::*;
use diesel::RunQueryDsl;
use model::{
schema::users,
user::{Device, Location, NewUser, User},
};
use std::sync::Arc;

use crate::{DbConn, DbPool};

async fn get_location(ip: &str) -> Result<Location, ()> {
actix_web::rt::System::new().block_on(async {
let client = Client::default();
client
.get(format!("http://ip-api.com/json/{ip}"))
.send()
.await
.map_err(|_| ())?
.json::<Location>()
.await
.map_err(|_| ())
})
}

async fn get_user(req: HttpRequest) -> Result<User, String> {
let time = Utc::now().naive_utc();
let ip = req.peer_addr().map(|addr| addr.ip().to_string());

let location = if let Some(ref ip) = ip {
get_location(ip).await.unwrap_or_default()
} else {
Location::default()
};

let ip = ip.unwrap_or_default();

let user_agent = req
.headers()
.get("User-Agent")
.and_then(|field| field.to_str().ok())
.map(|s| s.to_owned());

let device = user_agent.as_ref().map(Device::from).unwrap_or_default();

let Location {
country,
state,
city,
} = location;

Ok(User {
device,
ip,
user_agent,
time,
country,
state,
city,
})
}

async fn insert_user(user: User, mut conn: DbConn) {
let _ = web::block(move || {
diesel::insert_into(users::table)
.values(NewUser::from(&user))
.execute(&mut conn)
})
.await;
}

pub async fn log_user(req: HttpRequest, conn: DbConn) {
if let Ok(user) = get_user(req).await {
insert_user(user, conn).await
} // Otherwise do nothing! missing a visit is fine :)
}
1 change: 1 addition & 0 deletions backend/src/api/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod metrics;
pub mod order;
pub mod stock;
pub mod stripe;
42 changes: 27 additions & 15 deletions backend/src/api/stock.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use model::{
item::{self, Item, NewItem, NewQuantity, TableItem},
schema::{self, stock},
item::{self, Item, NewItem, TableItem},
schema::stock,
CartMap, ItemId,
};

Expand All @@ -14,6 +14,7 @@ use std::{collections::HashMap, sync::Arc};

use diesel::{prelude::*, r2d2::ConnectionManager};

use super::metrics::log_user;
use crate::DbPool;

use super::stripe::StripeItem;
Expand Down Expand Up @@ -54,7 +55,7 @@ pub async fn get_total(cart: Arc<CartMap>, pool: Arc<DbPool>) -> Result<u32> {
.map_err(|e| error::ErrorInternalServerError(format!("<fn GET_TOTAL>\n{e}")))?;

let id_kind_map = HashMap::<i32, i32>::from_iter(id_kind_pairs.into_iter());
Ok(cart.iter().try_fold(0, |total, (id, qty)| {
cart.iter().try_fold(0, |total, (id, qty)| {
if let Some(kind) = id_kind_map.get(&(*id as i32)) {
let price = match item::Kind::from(*kind) {
item::Kind::BigPrint => 20_00,
Expand All @@ -67,7 +68,7 @@ pub async fn get_total(cart: Arc<CartMap>, pool: Arc<DbPool>) -> Result<u32> {
"<fn GET_TOTAL>\nError fetching total - Item in cart not present in kind map",
))
}
})?)
})
}

#[get("/stock/{item_id}")]
Expand All @@ -79,23 +80,34 @@ pub async fn get_item(item_id: Path<u32>, pool: web::Data<DbPool>) -> Result<web
}

#[get("/stock")]
pub async fn get_stock(pool: web::Data<DbPool>) -> Result<HttpResponse> {
let stock = web::block(move || {
let mut conn = pool.get().map_err(|_| "couldn't get db connection")?;
stock::table
.select(TableItem::as_select())
.get_results::<TableItem>(&mut conn)
.map_err(|e| format!("Cannot fetch stock: {e}"))
})
.await?
.map_err(error::ErrorInternalServerError)?;
pub async fn get_stock(
req: actix_web::HttpRequest,
pool: web::Data<DbPool>,
) -> Result<HttpResponse> {
let stock = {
let mut stock_conn = pool
.get()
.map_err(|e| error::ErrorInternalServerError(format!("Cannot connect to DB: {e}")))?;
web::block(move || {
stock::table
.select(TableItem::as_select())
.get_results::<TableItem>(&mut stock_conn)
.map_err(|e| format!("Cannot fetch stock: {e}"))
})
.await?
.map_err(error::ErrorInternalServerError)?
};

let stock = stock
.into_iter()
.map(|item| (item.id as u32, Item::from(item)))
.collect::<HashMap<u32, Item>>();
let ser = to_string(&stock)?;

if let Ok(metrics_conn) = pool.get() {
actix_web::rt::spawn(log_user(req, metrics_conn));
}

Ok(HttpResponse::Ok()
.content_type("application/json")
.body(ser))
Expand Down Expand Up @@ -229,7 +241,7 @@ pub async fn get_title_map(cart: Arc<CartMap>, pool: DbPool) -> Result<HashMap<I
Ok(id_title_map)
})
.await?
.map_err(|e| error::ErrorInternalServerError(e))
.map_err(error::ErrorInternalServerError)
}

pub async fn get_matching_ids(ids: Vec<u32>, pool: Arc<DbPool>) -> Result<Vec<TableItem>> {
Expand Down
31 changes: 10 additions & 21 deletions backend/src/api/stripe.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use lettre::{AsyncSmtpTransport, Tokio1Executor};
use serde::{Deserialize, Serialize};
use std::{borrow::Borrow, collections::HashMap, num::ParseIntError, sync::Arc};
use std::{borrow::Borrow, collections::HashMap, sync::Arc};

use actix_web::{
error, post, rt,
Expand All @@ -21,20 +20,13 @@ use stripe::{
};

use crate::{
api::{
order::insert_order,
stock::{dec_items, get_title_map, get_total, item_from_db},
},
api::{order::insert_order, stock::dec_items},
mail,
utils::print_red,
DbPool, Env, Mailer,
};

use model::{
address::Address,
item::{Item, TableItem},
CartMap, ItemId, Quantity,
};
use model::{address::Address, ItemId, Quantity};

use super::stock::get_matching_ids;

Expand Down Expand Up @@ -80,16 +72,13 @@ pub async fn checkout(

let mut product_price_pairs = Vec::<(Price, u64)>::with_capacity(item_map.keys().len());

for (
_,
StripeItem {
title,
price,
quantity,
},
) in &item_map
for StripeItem {
title,
price,
quantity,
} in item_map.values()
{
let create_product = CreateProduct::new(&title);
let create_product = CreateProduct::new(title);
let product = Product::create(&client, create_product)
.await
.map_err(|e| error::ErrorInternalServerError(e.to_string()))?;
Expand Down Expand Up @@ -217,7 +206,7 @@ pub async fn parse_webhook(
if let Ok(event) = event {
if let EventType::CheckoutSessionCompleted = event.type_ {
if let EventObject::CheckoutSession(session) = event.data.object {
handle_checkout(session, pool, &*env, mailer.into_inner()).await?;
handle_checkout(session, pool, &env, mailer.into_inner()).await?;
}
}
} else {
Expand Down
2 changes: 1 addition & 1 deletion backend/src/mail/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub async fn send_confirmation(user: UserData, mailer: Arc<Mailer>) -> Result<()

let email = Message::builder()
.from("Kiggyshop <[email protected]>".parse().unwrap())
.to("Kiggy <kristencrankin@gmail.com>".parse().unwrap())
.to("Fay <faycarsons23@gmail.com>".parse().unwrap())
.subject("Thank you for your order!")
.multipart(
MultiPart::alternative()
Expand Down
39 changes: 19 additions & 20 deletions backend/src/mail/templates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::api::stripe::{StripeItem, UserData};

pub struct Item {
title: String,
price: f32,
price: f64,
quantity: u32,
total: u32,
}
Expand All @@ -15,7 +15,7 @@ impl From<(&model::item::Item, &Quantity)> for Item {
let price = item.price();
Self {
title: item.title.clone(),
price: price as f32 / 1000.,
price: price as f64 / 100f64,
quantity: *quantity,
total: price * quantity,
}
Expand All @@ -27,21 +27,26 @@ impl From<(&model::item::Item, &Quantity)> for Item {
pub struct Confirmation {
name: String,
address: String,
total: f32,
total: f64,
cart: Vec<Item>,
}

impl Confirmation {
pub fn render_plaintext(&self) -> String {
let Confirmation {
name,
address,
total,
total: order_total,
cart,
..
} = self;

let title = "Title";
let price = "Price";
let quantity = "Quantity";
let total = "Total";

let [title_width, price_width, quantity_width, total_width] = cart.iter().fold(
[5, 5, 8, 5],
[title.len(), price.len(), quantity.len(), total.len()],
|mut acc,
Item {
title,
Expand Down Expand Up @@ -90,7 +95,7 @@ impl Confirmation {

let order_details = cart
.iter()
.fold(format!("{separator}\n{header}"),
.fold(format!("{separator}\n{header}\n"),
|mut acc, Item {
title,
price,
Expand All @@ -106,20 +111,14 @@ impl Confirmation {

let total_box = format!(
"{separator}\n| {:>total_width$} |\n{separator}",
String::from("Total: ") + &total.to_string(),
total_width = title_width + price_width + quantity_width + total_width
String::from("Total: ") + &order_total.to_string(),
total_width = [title_width, price_width, quantity_width, total_width]
.into_iter()
.sum()
);

format!(
r#"
Thank you {name}!
We appreciate your support! Your order is currently being processed, a
shipping confirmation will be sent shortly.
{order_details}
{total_box}
"#
"Thank you {name}!\n\nWe appreciate your support! Your order is currently being processed, a shipping confirmation will be sent shortly.\n\n{order_details}\n{total_box}"
)
}
}
Expand All @@ -146,7 +145,7 @@ impl From<&UserData> for Confirmation {
},
)| Item {
title: title.clone(),
price: (*price as f32) / 100.,
price: (*price as f64) / 100f64,
quantity: *quantity,
total: *total,
},
Expand All @@ -161,7 +160,7 @@ impl From<&UserData> for Confirmation {
Confirmation {
name: name.clone(),
address,
total: (*total as f32) / 100.,
total: (*total as f64) / 100f64,
cart,
}
}
Expand Down
6 changes: 5 additions & 1 deletion backend/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,14 @@ use env::Env;

use actix_web::{middleware::Logger, web, App, HttpServer};

use diesel::{r2d2, SqliteConnection};
use diesel::{
r2d2::{self, ConnectionManager},
SqliteConnection,
};
use lettre::{transport::smtp::authentication::Credentials, AsyncSmtpTransport, Tokio1Executor};

pub type DbPool = r2d2::Pool<r2d2::ConnectionManager<SqliteConnection>>;
pub type DbConn = r2d2::PooledConnection<ConnectionManager<SqliteConnection>>;
pub type Mailer = AsyncSmtpTransport<Tokio1Executor>;

const ADDRESS_PORT: (&str, u16) = ("0.0.0.0", 3000);
Expand Down
4 changes: 2 additions & 2 deletions backend/src/tests/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ mod tests {

use crate::{
api::{
order::{self, delete_order},
order::{delete_order},
stock::get_stock,
},
tests::test_db,
};
use actix_web::{test, web, App};
use diesel::SqliteConnection;
use model::{
item::{Item, NewItem, TableItem},
item::{Item, NewItem},
order::{NewOrder, Order},
ItemId,
};
Expand Down
2 changes: 1 addition & 1 deletion backend/src/tests/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ mod tests {
dsl::count, query_dsl::methods::SelectDsl, ExpressionMethods, QueryDsl, RunQueryDsl,
};
use model::{
cart::{Cart, NewCart},
cart::NewCart,
item::{Item, NewItem},
order::{NewOrder, Order},
};
Expand Down
Loading

0 comments on commit ba0ac20

Please sign in to comment.