use crate::database::models::{ product_item, DatabaseError, ProductId, ProductPriceId, }; use crate::database::redis::RedisPool; use crate::models::billing::{Price, ProductMetadata}; use dashmap::DashMap; use itertools::Itertools; use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use std::convert::TryInto; const PRODUCTS_NAMESPACE: &str = "products"; pub struct ProductItem { pub id: ProductId, pub metadata: ProductMetadata, pub unitary: bool, } struct ProductResult { id: i64, metadata: serde_json::Value, unitary: bool, } macro_rules! select_products_with_predicate { ($predicate:tt, $param:ident) => { sqlx::query_as!( ProductResult, r#" SELECT id, metadata, unitary FROM products "# + $predicate, $param ) }; } impl TryFrom for ProductItem { type Error = serde_json::Error; fn try_from(r: ProductResult) -> Result { Ok(ProductItem { id: ProductId(r.id), metadata: serde_json::from_value(r.metadata)?, unitary: r.unitary, }) } } impl ProductItem { pub async fn get( id: ProductId, exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>, ) -> Result, DatabaseError> { Ok(Self::get_many(&[id], exec).await?.into_iter().next()) } pub async fn get_many( ids: &[ProductId], exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>, ) -> Result, DatabaseError> { let ids = ids.iter().map(|id| id.0).collect_vec(); let ids_ref: &[i64] = &ids; let results = select_products_with_predicate!( "WHERE id = ANY($1::bigint[])", ids_ref ) .fetch_all(exec) .await?; Ok(results .into_iter() .map(|r| r.try_into()) .collect::, serde_json::Error>>()?) } pub async fn get_all( exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>, ) -> Result, DatabaseError> { let one = 1; let results = select_products_with_predicate!("WHERE 1 = $1", one) .fetch_all(exec) .await?; Ok(results .into_iter() .map(|r| r.try_into()) .collect::, serde_json::Error>>()?) } } #[derive(Deserialize, Serialize)] pub struct QueryProduct { pub id: ProductId, pub metadata: ProductMetadata, pub unitary: bool, pub prices: Vec, } impl QueryProduct { pub async fn list<'a, E>( exec: E, redis: &RedisPool, ) -> Result, DatabaseError> where E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy, { let mut redis = redis.connect().await?; let res: Option> = redis .get_deserialized_from_json(PRODUCTS_NAMESPACE, "all") .await?; if let Some(res) = res { return Ok(res); } let all_products = product_item::ProductItem::get_all(exec).await?; let prices = product_item::ProductPriceItem::get_all_products_prices( &all_products.iter().map(|x| x.id).collect::>(), exec, ) .await?; let products = all_products .into_iter() .map(|x| QueryProduct { id: x.id, metadata: x.metadata, prices: prices .remove(&x.id) .map(|x| x.1) .unwrap_or_default() .into_iter() .map(|x| ProductPriceItem { id: x.id, product_id: x.product_id, prices: x.prices, currency_code: x.currency_code, }) .collect(), unitary: x.unitary, }) .collect::>(); redis .set_serialized_to_json(PRODUCTS_NAMESPACE, "all", &products, None) .await?; Ok(products) } } #[derive(Deserialize, Serialize)] pub struct ProductPriceItem { pub id: ProductPriceId, pub product_id: ProductId, pub prices: Price, pub currency_code: String, } struct ProductPriceResult { id: i64, product_id: i64, prices: serde_json::Value, currency_code: String, } macro_rules! select_prices_with_predicate { ($predicate:tt, $param:ident) => { sqlx::query_as!( ProductPriceResult, r#" SELECT id, product_id, prices, currency_code FROM products_prices "# + $predicate, $param ) }; } impl TryFrom for ProductPriceItem { type Error = serde_json::Error; fn try_from(r: ProductPriceResult) -> Result { Ok(ProductPriceItem { id: ProductPriceId(r.id), product_id: ProductId(r.product_id), prices: serde_json::from_value(r.prices)?, currency_code: r.currency_code, }) } } impl ProductPriceItem { pub async fn get( id: ProductPriceId, exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>, ) -> Result, DatabaseError> { Ok(Self::get_many(&[id], exec).await?.into_iter().next()) } pub async fn get_many( ids: &[ProductPriceId], exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>, ) -> Result, DatabaseError> { let ids = ids.iter().map(|id| id.0).collect_vec(); let ids_ref: &[i64] = &ids; let results = select_prices_with_predicate!( "WHERE id = ANY($1::bigint[])", ids_ref ) .fetch_all(exec) .await?; Ok(results .into_iter() .map(|r| r.try_into()) .collect::, serde_json::Error>>()?) } pub async fn get_all_product_prices( product_id: ProductId, exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>, ) -> Result, DatabaseError> { let res = Self::get_all_products_prices(&[product_id], exec).await?; Ok(res.remove(&product_id).map(|x| x.1).unwrap_or_default()) } pub async fn get_all_products_prices( product_ids: &[ProductId], exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>, ) -> Result>, DatabaseError> { let ids = product_ids.iter().map(|id| id.0).collect_vec(); let ids_ref: &[i64] = &ids; use futures_util::TryStreamExt; let prices = select_prices_with_predicate!( "WHERE product_id = ANY($1::bigint[])", ids_ref ) .fetch(exec) .try_fold( DashMap::new(), |acc: DashMap>, x| { if let Ok(item) = >::try_into(x) { acc.entry(item.product_id).or_default().push(item); } async move { Ok(acc) } }, ) .await?; Ok(prices) } }