pharmacy/src/api.rs

353 lines
12 KiB
Rust

use rocket::serde::json::Json;
use rocket::{form::Form, http::CookieJar, State};
use rand::Rng;
use uuid::Uuid;
use crate::authenticate::*;
use crate::database::*;
use crate::structures::*;
use crate::html::{render_product, render_products};
use argon2::{password_hash::SaltString, Argon2};
// Endpoint to add a new product
#[post("/api/new", data = "<request>")]
pub fn new_product(argon2: &State<Argon2>, request: Form<ProductRequest>) -> Json<Status> {
/* Need to rework the password mechanism so it's not stored in plaintext serverside */
if check_password(&request.password, &argon2) {
// Check if the password is correct
let new_product: Product = Product {
// Create a new Product object from the supplied info
short_name: request.short_name.clone(),
full_name: request.full_name.clone(),
description: request.description.clone(),
price_usd: request.price_usd,
stock: request.stock,
class: request.class,
};
match register_product(&new_product.short_name, &new_product) {
// Register the new product in the database
Ok(s) => info!("{}", s),
Err(error) => {
return Json(Status {
status: "fail".to_string(),
reason: error.to_string(),
})
}
}
render_products();
render_product(new_product);
return Json(Status {
// Return a JSON status
status: "success".to_string(),
reason: "registered new product".to_string(),
});
};
return Json(Status {
// Return a JSON status fail
status: "fail".to_string(),
reason: "wrong password".to_string(),
});
}
// Endpoint to place a new order
#[post("/api/order", data = "<order>")]
pub fn new_order(cookies: &CookieJar<'_>, order: Form<OrderRequest<'_>>) -> String {
let mut rng = rand::thread_rng(); // Get a new random number
let to_hash = format!(
"{}{}{}",
order.user_message.to_string(),
order.encryption_key.to_string(),
rng.gen::<f64>().to_string()
); // Create a has from various components of the order + a random number
let user_token = cookies.get_private("token").unwrap();
if user_token.value() == order.captcha {
let db_order: Order = Order {
name: order.name.to_string(),
message: order.user_message.to_string(),
encryption_key: order.encryption_key.to_string(),
email: order.email.to_string(),
payment_type: order.payment_type.clone(),
uuid: Uuid::new_v5(&Uuid::NAMESPACE_X500, to_hash.as_bytes()),
status: OrderStatus::Submitted,
};
match make_order(&db_order) {
Ok(s) => info!("{}", s),
Err(error) => {
return format!("Failed to submit order: {:?}", error);
}
}
return format!(
"Thank you for ordering from Catgirl Pharmacy!\nYour order Uuid is {}",
db_order.uuid.to_hyphenated().to_string()
);
} else {
return "Incorrect CAPTCHA answer.\nPlease try again!".to_string();
}
}
// Modify an order
#[post("/api/order/update", data = "<update>")]
pub fn update_order(argon2: &State<Argon2>, update: Form<OrderUpdate<'_>>) -> Json<Status> {
if check_password(&update.password, &argon2) {
let mut new_order = match read_order(&update.order_uuid) {
Ok(order) => order,
Err(error) => {
return Json(Status {
status: "fail".to_string(),
reason: error.to_string(),
})
}
};
new_order.status = update.status;
new_order.payment_type = update.payment_type;
match make_order(&new_order) {
Ok(s) => info!("{}", s),
Err(error) => {
return Json(Status {
status: "fail".to_string(),
reason: error.to_string(),
})
}
}
return Json(Status {
status: "success".to_string(),
reason: "updated order".to_string(),
});
} else {
return Json(Status {
status: "fail".to_string(),
reason: "wrong password".to_string(),
});
}
}
// Get the details of a specific order from it's Uuid
#[get("/api/order/<uuid>")]
pub fn order_info(uuid: &str) -> Result<Json<Order>, Json<Status>> {
let order = match read_order(uuid) {
Ok(order) => order,
Err(error) => {
return Err(Json(Status {
status: "fail".to_string(),
reason: error.to_string(),
}));
}
}; // Read the order from the database
return Ok(Json(order)); // Serialize it into json & return
}
// Set the admin password
#[post("/api/password", data = "<auth>")]
pub fn set_password(
auth: Form<Authenticate>,
argon2: &State<Argon2>,
salt: &State<SaltString>,
) -> Json<Status> {
if admin_password_exists() {
return Json(Status {
status: "fail".to_string(),
reason: "password already exists".to_string(),
});
} else {
set_admin_password(&auth.password, &salt, &argon2);
return Json(Status {
status: "success".to_string(),
reason: "set admin password".to_string(),
});
}
}
// Get all orders made
#[post("/api/orders", data = "<auth>")]
pub fn all_orders(
auth: Form<Authenticate>,
argon2: &State<Argon2>,
) -> Result<Json<Vec<Order>>, Json<Status>> {
if check_password(&auth.password, &argon2) {
let all_orders = match read_all_orders() {
Ok(orders) => orders,
Err(error) => {
return Err(Json(Status {
status: "fail".to_string(),
reason: error.to_string(),
}))
}
}; // Read all orders from the database
let mut orders_list: Vec<Order> = Vec::new(); // Create a new vector of Order's
for x in all_orders {
// Loop through all read orders
orders_list.push(x); // Push each entry to the vector
}
return Ok(Json(orders_list)); // Return the orders serialized as json
} else {
return Err(Json(Status {
// Fail on wrong password
status: "fail".to_string(),
reason: "wrong password".to_string(),
}));
}
}
// Delete an order
#[post("/api/delete/<order>", data = "<auth>")]
pub fn delete_order(auth: Form<Authenticate>, order: &str, argon2: &State<Argon2>) -> Json<Status> {
if check_password(&auth.password, &argon2) {
match remove_order(&order) {
Ok(s) => info!("{}", s),
Err(error) => {
return Json(Status {
status: "fail".to_string(),
reason: error.to_string(),
})
}
};
return Json(Status {
status: "success".to_string(),
reason: "deleted order".to_string(),
});
} else {
return Json(Status {
status: "fail".to_string(),
reason: "wrong password".to_string(),
});
}
}
// Update fields of a product
#[post("/api/update", data = "<update>")]
pub fn update_product(argon2: &State<Argon2>, update: Form<Update<'_>>) -> Json<Status> {
if check_password(&update.password, &argon2) {
match update.field {
// Check what field is being updated
UpdateField::Price => {
let mut new_product = match read_product(&update.product) {
// Read the product from the database
Ok(product) => product,
Err(error) => {
return Json(Status {
status: "fail".to_string(),
reason: error.to_string(),
});
}
};
new_product.price_usd = update.value.unwrap(); // Change the price_usd value to the provided value
match register_product(&update.product, &new_product) {
// Update the product in the database
Ok(s) => info!("{}", s),
Err(error) => {
return Json(Status {
status: "fail".to_string(),
reason: error.to_string(),
})
}
}
render_product(new_product);
return Json(Status {
// Return a json success status
status: "success".to_string(),
reason: "price changed".to_string(),
});
}
UpdateField::Stock => {
// Same as before, but with stock
let mut new_product = match read_product(&update.product) {
Ok(product) => product,
Err(error) => {
return Json(Status {
status: "fail".to_string(),
reason: error.to_string(),
});
}
};
new_product.stock = update.value.unwrap() as u32;
match register_product(&update.product, &new_product) {
Ok(s) => info!("{}", s),
Err(error) => {
return Json(Status {
status: "fail".to_string(),
reason: error.to_string(),
})
}
}
render_product(new_product);
return Json(Status {
status: "success".to_string(),
reason: "stock changed".to_string(),
});
}
UpdateField::Description => {
// Update the description
let mut new_product = match read_product(&update.product) {
Ok(product) => product,
Err(error) => {
return Json(Status {
status: "fail".to_string(),
reason: error.to_string(),
});
}
};
new_product.description = update.text.unwrap().to_string();
println!("Description: {}", &new_product.description);
match register_product(&update.product, &new_product) {
Ok(s) => info!("{}", s),
Err(error) => {
return Json(Status {
status: "fail".to_string(),
reason: error.to_string(),
})
}
}
render_product(new_product);
return Json(Status {
status: "success".to_string(),
reason: "description changed".to_string(),
});
}
_ => {
// Fail because provided field wasn't found, return json
return Json(Status {
status: "fail".to_string(),
reason: "field not found".to_string(),
});
}
}
};
return Json(Status {
// Return JSON fail due to wrong password
status: "fail".to_string(),
reason: "wrong password".to_string(),
});
}
// Get all data about a product
#[get("/api/product/<product>")]
pub fn get_product_info(product: &str) -> Json<Product> {
return Json(match read_product(&product) {
Ok(product) => product,
Err(error) => panic!("{:?}", error),
});
}