// SPDX-FileCopyrightText: 2024 Aravinth Manivannan // // SPDX-License-Identifier: AGPL-3.0-or-later use std::str::FromStr; use async_trait::async_trait; use cqrs_es::persist::{PersistenceError, ViewContext, ViewRepository}; use cqrs_es::{EventEnvelope, Query, View}; use serde::{Deserialize, Serialize}; use uuid::Uuid; use super::errors::*; use super::InventoryDBPostgresAdapter; use crate::inventory::domain::{customization_aggregate::*, events::InventoryEvent}; use crate::utils::parse_aggregate_id::parse_aggregate_id; pub const NEW_CUSTOMIZATION_NON_UUID: &str = "new_customization_non_uuid-asdfa"; //#[derive(Debug, Default, Serialize, Deserialize)] //struct Customizations { // customizations: Vec, //} #[derive(Debug, Default, Serialize, Deserialize)] struct CustomizationView { name: String, product_id: Uuid, customization_id: Uuid, deleted: bool, } impl From for Customization { fn from(v: CustomizationView) -> Self { CustomizationBuilder::default() .name(v.name) .customization_id(v.customization_id) .product_id(v.product_id) .deleted(v.deleted) .build() .unwrap() } } // This updates the view with events as they are committed. // The logic should be minimal here, e.g., don't calculate the account balance, // design the events to carry the balance information instead. impl View for CustomizationView { fn update(&mut self, event: &EventEnvelope) { match &event.payload { InventoryEvent::CustomizationAdded(val) => { self.name = val.customization().name().into(); self.product_id = *val.customization().product_id(); self.customization_id = *val.customization().customization_id(); self.deleted = false; } InventoryEvent::CustomizationUpdated(e) => { let new = e.new_customization(); self.name = new.name().into(); self.product_id = *new.product_id(); self.customization_id = *new.customization_id(); } _ => (), } } } #[async_trait] impl ViewRepository for InventoryDBPostgresAdapter { async fn load( &self, customization_id: &str, ) -> Result, PersistenceError> { let customization_id = match parse_aggregate_id(customization_id, NEW_CUSTOMIZATION_NON_UUID)? { Some((val, _)) => return Ok(Some(val)), None => Uuid::parse_str(customization_id).unwrap(), }; let res = sqlx::query_as!( CustomizationView, "SELECT name, customization_id, product_id, deleted FROM cqrs_inventory_product_customizations_query WHERE customization_id = $1;", customization_id ) .fetch_one(&self.pool) .await .map_err(PostgresAggregateError::from)?; // let customizations = res.get_customizations(&self).await?; Ok(Some(res)) } async fn load_with_context( &self, customization_id: &str, ) -> Result, PersistenceError> { let customization_id = match parse_aggregate_id(customization_id, NEW_CUSTOMIZATION_NON_UUID)? { Some(val) => return Ok(Some(val)), None => Uuid::parse_str(customization_id).unwrap(), }; let res = sqlx::query_as!( CustomizationView, "SELECT name, customization_id, product_id, deleted FROM cqrs_inventory_product_customizations_query WHERE customization_id = $1;", customization_id ) .fetch_one(&self.pool) .await .map_err(PostgresAggregateError::from)?; // let customizations = res.get_customizations(&self).await?; struct Context { version: i64, customization_id: Uuid, } let ctx = sqlx::query_as!( Context, "SELECT customization_id, version FROM cqrs_inventory_product_customizations_query WHERE customization_id = $1;", customization_id ) .fetch_one(&self.pool) .await .map_err(PostgresAggregateError::from)?; let view_context = ViewContext::new(ctx.customization_id.to_string(), ctx.version); Ok(Some((res, view_context))) } async fn update_view( &self, view: CustomizationView, context: ViewContext, ) -> Result<(), PersistenceError> { match context.version { 0 => { let version = context.version + 1; sqlx::query!( "INSERT INTO cqrs_inventory_product_customizations_query ( version, name, customization_id, product_id, deleted ) VALUES ( $1, $2, $3, $4, $5 );", version, view.name, view.customization_id, view.product_id, view.deleted, ) .execute(&self.pool) .await .map_err(PostgresAggregateError::from)?; } _ => { let version = context.version + 1; sqlx::query!( "UPDATE cqrs_inventory_product_customizations_query SET version = $1, name = $2, customization_id = $3, product_id = $4, deleted = $5;", version, view.name, view.customization_id, view.product_id, view.deleted, ) .execute(&self.pool) .await .map_err(PostgresAggregateError::from)?; } } Ok(()) } } #[async_trait] impl Query for InventoryDBPostgresAdapter { async fn dispatch(&self, customization_id: &str, events: &[EventEnvelope]) { let res = self .load_with_context(&customization_id) .await .unwrap_or_else(|_| { Some(( CustomizationView::default(), ViewContext::new(customization_id.into(), 0), )) }); let (mut view, view_context): (CustomizationView, ViewContext) = res.unwrap(); for event in events { view.update(event); } self.update_view(view, view_context).await.unwrap(); } }