move to rocket + refactor

This commit is contained in:
JMARyA 2024-06-21 21:14:45 +02:00
parent b0e80dd3e8
commit b8caa43972
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
11 changed files with 686 additions and 886 deletions

1258
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -4,18 +4,16 @@ version = "0.1.0"
edition = "2021"
[dependencies]
actix-web = "4.4.1"
chrono = "0.4.38"
env_logger = "0.10.1"
futures = "0.3.30"
log = "0.4.20"
maud = "0.25.0"
mdq = { git = "https://git.hydrar.de/mdtools/mdq", version = "0.3.0" }
mdq = { git = "https://git.hydrar.de/mdtools/mdq" }
mongodb = "2.8.0"
rocket = { version = "0.5.1", features = ["json"] }
rocket_cors = "0.6.0"
serde = { version = "1.0.195", features = ["derive"] }
serde_json = "1.0.111"
serde_yaml = "0.9.34"
tokio = { version = "1.35.1", features = ["full"] }
toml = "0.8.8"
uuid = { version = "1.8.0", features = ["v4"] }
web-base = "0.2.2"

3
rocket.toml Normal file
View file

@ -0,0 +1,3 @@
[default]
address = "0.0.0.0"
port = 8080

View file

@ -1,4 +1,4 @@
use mongodb::{bson::doc, ClientSession};
use mongodb::bson::doc;
use crate::get_mongo;
@ -16,8 +16,7 @@ impl InventoryCache {
.return_document(mongodb::options::ReturnDocument::After)
.build();
let result = db
.database("cdb")
db.database("cdb")
.collection::<mongodb::bson::Document>("cache")
.find_one_and_update(doc! { "_id": "inventory"}, update, options)
.await
@ -51,8 +50,7 @@ impl InventoryCache {
.return_document(mongodb::options::ReturnDocument::After)
.build();
let result = db
.database("cdb")
db.database("cdb")
.collection::<mongodb::bson::Document>("cache")
.find_one_and_update(doc! { "_id": "inventory"}, update, options)
.await

View file

@ -49,7 +49,7 @@ impl ItemDB {
let mongodb = get_mongo!();
for item in &index.documents {
let item = ItemEntry::new(item.clone());
let item = ItemEntry::new(item);
item.init_db(&mongodb).await;
}
@ -62,7 +62,7 @@ impl ItemDB {
self.index
.documents
.iter()
.map(|x| ItemEntry::new(x.clone()))
.map(ItemEntry::new)
.find(|x| x.name == item)?,
))
}
@ -71,7 +71,7 @@ impl ItemDB {
pub fn items(&self) -> Vec<String> {
let mut ret = vec![];
for item in &self.index.documents {
let item = ItemEntry::new(item.clone());
let item = ItemEntry::new(item);
ret.push(item.name);
}
ret

View file

@ -32,7 +32,7 @@ pub struct ItemEntry {
}
impl ItemEntry {
pub fn new(doc: mdq::Document) -> Self {
pub fn new(doc: &mdq::Document) -> Self {
let name = std::path::Path::new(&doc.path)
.file_stem()
.unwrap()
@ -56,13 +56,12 @@ impl ItemEntry {
.as_mapping()
.unwrap()
.get("variants")
.unwrap()
.as_mapping()
.unwrap()
.map(|x| x.as_mapping().unwrap().clone())
.unwrap_or_default()
{
variants.insert(
variant_name.as_str().unwrap().to_string(),
Variant::from_yml(variant, variant_name.as_str().unwrap(), &name),
Variant::from_yml(&variant, variant_name.as_str().unwrap(), &name),
);
}
@ -111,15 +110,15 @@ pub struct Item {
}
impl Item {
pub fn new(item: ItemEntry) -> Self {
pub const fn new(item: ItemEntry) -> Self {
Self { item }
}
pub async fn get_variants(&self) -> Vec<String> {
pub fn get_variants(&self) -> Vec<String> {
self.item.variants.keys().cloned().collect()
}
pub async fn variant(&self, variant: &str) -> Option<Variant> {
self.item.variants.get(variant).map(|x| x.clone())
pub fn variant(&self, variant: &str) -> Option<Variant> {
self.item.variants.get(variant).cloned()
}
}

View file

@ -1,4 +1,5 @@
use actix_web::{get, HttpRequest, Responder};
use rocket::routes as route;
use rocket::{http::Method, launch};
mod cache;
mod db;
@ -22,31 +23,35 @@ mod variant;
// ░████▀░░░▀▀█████▄▄▄▄█████████▄░░
// ░░▀▀░░░░░░░░░▀▀██████▀▀░░░▀▀██░░
#[get("/")]
pub(crate) async fn index(r: HttpRequest) -> impl Responder {
let itemdb: &actix_web::web::Data<db::ItemDB> = r.app_data().unwrap();
let content = "";
web_base::func::build_site(&r, "Index", &content)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
env_logger::init();
#[launch]
async fn rocket() -> _ {
let cors = rocket_cors::CorsOptions {
allowed_origins: rocket_cors::AllowedOrigins::all(),
allowed_methods: vec![Method::Get, Method::Post, Method::Options]
.into_iter()
.map(From::from)
.collect(),
allowed_headers: rocket_cors::AllowedHeaders::all(),
allow_credentials: true,
..Default::default()
}
.to_cors()
.expect("error creating CORS options");
let itemdb = db::ItemDB::new("./itemdb").await;
let itemdb = actix_web::web::Data::new(itemdb);
web_base::map!(web_base::Site::new(), |app: actix_web::App<_>| {
app.app_data(itemdb.clone())
.service(index)
.service(routes::item::supply_route) // /supply
.service(routes::item::item_variants_page) // /item/{item_id}/variants
.service(routes::item::get_items_route) // /items
.service(routes::item::demand_route) // /demand
.service(routes::item::supply_log_route) // /item/{item_id}/{variant_id}/supply
.service(routes::item::demand_log_route) // /item/{item_id}/{variant_id}/demand
})
.bind(("0.0.0.0".to_string(), 8080))?
.run()
.await
rocket::build()
.mount(
"/",
route![
routes::item::get_items_route,
routes::item::item_variants_page,
routes::item::supply_log_route,
routes::item::demand_log_route,
routes::item::supply_route,
routes::item::demand_route,
],
)
.manage(itemdb)
.attach(cors)
}

View file

@ -1,24 +1,20 @@
use actix_web::post;
use actix_web::{get, HttpRequest, HttpResponse, Responder};
use rocket::serde::json::Json;
use rocket::State;
use rocket::{get, post};
use serde::Deserialize;
use serde_json::json;
use crate::item;
use crate::routes::bad_req;
use crate::db::ItemDB;
use crate::variant::Variant;
macro_rules! get_itemdb {
($req:expr) => {{
let itemdb: &actix_web::web::Data<crate::db::ItemDB> = $req.app_data().unwrap();
itemdb
}};
use super::{api_error, ApiError, FallibleApiResponse};
pub fn item_does_not_exist_error() -> ApiError {
api_error("The item does not exist")
}
pub fn item_does_not_exist_error() -> actix_web::Error {
bad_req("The item does not exist")
}
pub fn variant_does_not_exist_error() -> actix_web::Error {
bad_req("The item does not exist")
pub fn variant_does_not_exist_error() -> ApiError {
api_error("The item does not exist")
}
#[derive(Debug, Deserialize)]
@ -28,24 +24,20 @@ pub struct DemandForm {
price: String,
}
#[post("/demand")]
pub async fn demand_route(
req: HttpRequest,
f: actix_web::web::Form<DemandForm>,
) -> actix_web::Result<impl Responder> {
let itemdb = get_itemdb!(req);
#[post("/demand", data = "<f>")]
pub async fn demand_route(f: Json<DemandForm>) -> FallibleApiResponse {
let uuid = Variant::demand(
&f.uuid,
f.price
.clone()
.try_into()
.map_err(|_| bad_req("Price malformed"))?,
.map_err(|()| api_error("Price malformed"))?,
&f.destination,
)
.await
.ok_or_else(|| bad_req("Demand failed"))?;
.ok_or_else(|| api_error("Demand failed"))?;
Ok(actix_web::HttpResponse::Ok().json(serde_json::json!({"uuid": uuid})))
Ok(json!({"uuid": uuid}))
}
#[derive(Deserialize, Debug)]
pub struct SupplyForm {
@ -56,18 +48,13 @@ pub struct SupplyForm {
origin: String,
}
#[post("/supply")]
pub async fn supply_route(
req: HttpRequest,
form: actix_web::web::Form<SupplyForm>,
) -> actix_web::Result<impl Responder> {
let itemdb = get_itemdb!(req);
#[post("/supply", data = "<form>")]
pub async fn supply_route(form: Json<SupplyForm>, itemdb: &State<ItemDB>) -> FallibleApiResponse {
println!("{form:?}");
let variant = itemdb
.get_item(&form.item)
.ok_or_else(item_does_not_exist_error)?
.variant(&form.variant)
.await
.ok_or_else(variant_does_not_exist_error)?;
let transaction_id = variant
@ -76,72 +63,63 @@ pub async fn supply_route(
form.price
.clone()
.try_into()
.map_err(|_| bad_req("Price malformed"))?,
.map_err(|()| api_error("Price malformed"))?,
&form.origin,
)
.await;
Ok(actix_web::HttpResponse::Ok().json(serde_json::json!({"uuid": transaction_id})))
Ok(json!({"uuid": transaction_id}))
}
#[get("/items")]
pub async fn get_items_route(r: HttpRequest) -> impl Responder {
let itemdb = get_itemdb!(r);
pub fn get_items_route(itemdb: &State<ItemDB>) -> serde_json::Value {
let items = itemdb.items();
actix_web::HttpResponse::Ok().json(serde_json::json!({"items": items}))
json!({"items": items})
}
#[derive(Deserialize, Debug)]
pub struct AddVariantForm {
pub variant: String,
pub amount: usize,
}
#[get("/item/<item_id>/variants")]
pub fn item_variants_page(item_id: &str, itemdb: &State<ItemDB>) -> FallibleApiResponse {
let item = itemdb
.get_item(item_id)
.ok_or_else(item_does_not_exist_error)?;
let variants = item.get_variants();
#[get("/item/{item_id}/variants")]
pub async fn item_variants_page(r: HttpRequest) -> actix_web::Result<impl Responder> {
let id = r.match_info().query("item_id");
let itemdb = get_itemdb!(r);
let item = itemdb.get_item(id).ok_or_else(item_does_not_exist_error)?;
let variants = item.get_variants().await;
Ok(HttpResponse::Ok().json(serde_json::json!({
"item": id,
Ok(json!({
"item": item_id,
"variants": variants
})))
}))
}
#[get("/item/{item_id}/{variant_id}/supply")]
pub async fn supply_log_route(r: HttpRequest) -> actix_web::Result<impl Responder> {
let itemdb = get_itemdb!(r);
let item_id = r.match_info().query("item_id");
let variant_id = r.match_info().query("variant_id");
#[get("/item/<item_id>/<variant_id>/supply")]
pub async fn supply_log_route(
item_id: &str,
variant_id: &str,
itemdb: &State<ItemDB>,
) -> FallibleApiResponse {
let variant = itemdb
.get_item(item_id)
.ok_or_else(item_does_not_exist_error)?
.variant(variant_id)
.await
.ok_or_else(variant_does_not_exist_error)?;
let transactions = variant.supply_log().await;
Ok(HttpResponse::Ok().json(serde_json::json!(transactions)))
Ok(json!(transactions))
}
#[get("/item/{item_id}/{variant_id}/demand")]
pub async fn demand_log_route(r: HttpRequest) -> actix_web::Result<impl Responder> {
let itemdb = get_itemdb!(r);
let item_id = r.match_info().query("item_id");
let variant_id = r.match_info().query("variant_id");
#[get("/item/<item_id>/<variant_id>/demand")]
pub async fn demand_log_route(
item_id: &str,
variant_id: &str,
itemdb: &State<ItemDB>,
) -> FallibleApiResponse {
let variant = itemdb
.get_item(item_id)
.ok_or_else(item_does_not_exist_error)?
.variant(variant_id)
.await
.ok_or_else(variant_does_not_exist_error)?;
let transactions = variant.demand_log().await;
Ok(HttpResponse::Ok().json(serde_json::json!(transactions)))
Ok(json!(transactions))
}

View file

@ -1,5 +1,13 @@
use rocket::response::status::BadRequest;
use serde_json::json;
pub mod item;
pub fn bad_req(msg: &'static str) -> actix_web::Error {
actix_web::error::ErrorBadRequest(msg)
type ApiError = BadRequest<serde_json::Value>;
type FallibleApiResponse = Result<serde_json::Value, ApiError>;
fn api_error(msg: &str) -> ApiError {
BadRequest(json!({
"error": msg
}))
}

View file

@ -3,14 +3,14 @@ pub enum TransactionType {
Normal(Transaction),
}
impl TransactionType {
pub fn from(b: mongodb::bson::Document) -> Self {
if let Ok(kind) = b.get_str("kind") {
impl From<mongodb::bson::Document> for TransactionType {
fn from(value: mongodb::bson::Document) -> Self {
if let Ok(kind) = value.get_str("kind") {
if kind == "batch" {
return Self::Batch(BatchTransaction::from(b));
return Self::Batch(value.into());
}
}
Self::Normal(Transaction::from(b))
Self::Normal(value.into())
}
}
@ -40,12 +40,14 @@ impl Transaction {
"_id": &self.uuid,
"item": &self.item,
"variant": &self.variant,
"price": self.price.as_bson(),
"price": Into::<mongodb::bson::Bson>::into(self.price.clone()),
"origin": &self.origin,
"timestamp": self.timestamp
}
}
}
impl From<mongodb::bson::Document> for Transaction {
fn from(b: mongodb::bson::Document) -> Self {
let uuid = b.get_str("oid").unwrap().to_string();
let item = b.get_str("item").unwrap().to_string();
@ -64,6 +66,19 @@ impl Transaction {
}
}
impl From<Transaction> for mongodb::bson::Document {
fn from(value: Transaction) -> Self {
mongodb::bson::doc! {
"_id": &value.uuid,
"item": &value.item,
"variant": &value.variant,
"price": Into::<mongodb::bson::Bson>::into(value.price),
"origin": &value.origin,
"timestamp": value.timestamp
}
}
}
pub struct BatchTransaction {
pub uuid: String,
pub transactions: Vec<String>,
@ -76,27 +91,32 @@ impl BatchTransaction {
transactions,
}
}
}
pub fn as_doc(&self) -> mongodb::bson::Document {
mongodb::bson::doc! {
"_id": &self.uuid,
"kind": "batch",
"transactions": &self.transactions
}
}
fn from(b: mongodb::bson::Document) -> Self {
let uuid = b.get_str("_id").unwrap().to_string();
let transactions = b
impl From<mongodb::bson::Document> for BatchTransaction {
fn from(value: mongodb::bson::Document) -> Self {
let uuid = value.get_str("_id").unwrap().to_string();
let transactions = value
.get_array("transactions")
.unwrap()
.into_iter()
.iter()
.map(|x| x.as_str().unwrap().to_string())
.collect();
Self { uuid, transactions }
}
}
impl From<BatchTransaction> for mongodb::bson::Document {
fn from(value: BatchTransaction) -> Self {
mongodb::bson::doc! {
"_id": value.uuid,
"kind": "batch",
"transactions": value.transactions
}
}
}
#[derive(Debug, Clone)]
pub struct Price {
pub value: f64,
@ -119,11 +139,13 @@ impl Price {
currency: currency.to_string(),
})
}
}
pub fn as_bson(&self) -> mongodb::bson::Bson {
impl From<Price> for mongodb::bson::Bson {
fn from(value: Price) -> Self {
mongodb::bson::bson!({
"value": self.value,
"currency": self.currency.clone()
"value": value.value,
"currency": value.currency
})
}
}

View file

@ -34,8 +34,7 @@ impl Variant {
.as_mapping()
.unwrap()
.get("amount")
.map(|x| x.as_u64().unwrap())
.unwrap_or(1),
.map_or(1, |x| x.as_u64().unwrap()),
depends: json
.as_mapping()
.unwrap()
@ -43,11 +42,11 @@ impl Variant {
.map(|x| {
x.as_sequence()
.unwrap()
.into_iter()
.iter()
.map(|x| x.as_str().unwrap().to_string())
.collect()
})
.unwrap_or(Vec::new()),
.unwrap_or_default(),
}
}
@ -104,14 +103,10 @@ impl Variant {
// check if supply transaction exists
let supply_t = cdb_col!(db, "supply");
if supply_t
.find_one(doc! { "_id": uuid }, None)
supply_t
.find_one(doc! { "_id": uuid}, None)
.await
.unwrap()
.is_none()
{
return None;
}
.unwrap()?;
// todo : demand batch
@ -122,7 +117,7 @@ impl Variant {
doc! {
"_id": uuid,
"destination": destination,
"price": price.as_bson()
"price": Into::<mongodb::bson::Bson>::into(price)
},
None,
)
@ -163,7 +158,7 @@ impl Variant {
}
for transaction in &transactions {
let r = col.insert_one(transaction.as_doc(), None).await.unwrap();
col.insert_one(transaction.as_doc(), None).await.unwrap();
// update cache
InventoryCache::push(&transaction.uuid).await;
@ -177,8 +172,12 @@ impl Variant {
BatchTransaction::new(transactions.iter().map(|x| x.uuid.clone()).collect());
let col: mongodb::Collection<mongodb::bson::Document> =
db.database("cdb").collection("transactions_batch");
col.insert_one(batch.as_doc(), None).await.unwrap();
batch.uuid
let batch_uuid = batch.uuid.clone();
col.insert_one(Into::<mongodb::bson::Document>::into(batch), None)
.await
.unwrap();
batch_uuid
};
ret_uuid