From 92a22100bef62755c743e9dd9e04ef67a7d6f7ba Mon Sep 17 00:00:00 2001 From: Aravinth Manivannan Date: Wed, 18 Dec 2024 18:14:29 +0530 Subject: [PATCH] feat: bootstrap add bill web interface --- src/billing/adapters/input/mod.rs | 1 + src/billing/adapters/input/web/bill.rs | 168 +++++++++++++++++++++++ src/billing/adapters/input/web/errors.rs | 75 ++++++++++ src/billing/adapters/input/web/mod.rs | 28 ++++ src/billing/adapters/input/web/routes.rs | 67 +++++++++ src/billing/adapters/output/mod.rs | 2 +- src/billing/mod.rs | 2 +- 7 files changed, 341 insertions(+), 2 deletions(-) create mode 100644 src/billing/adapters/input/web/bill.rs create mode 100644 src/billing/adapters/input/web/errors.rs create mode 100644 src/billing/adapters/input/web/mod.rs create mode 100644 src/billing/adapters/input/web/routes.rs diff --git a/src/billing/adapters/input/mod.rs b/src/billing/adapters/input/mod.rs index 56f60de..810dc4e 100644 --- a/src/billing/adapters/input/mod.rs +++ b/src/billing/adapters/input/mod.rs @@ -1,3 +1,4 @@ // SPDX-FileCopyrightText: 2024 Aravinth Manivannan // // SPDX-License-Identifier: AGPL-3.0-or-later +pub mod web; diff --git a/src/billing/adapters/input/web/bill.rs b/src/billing/adapters/input/web/bill.rs new file mode 100644 index 0000000..c9e58df --- /dev/null +++ b/src/billing/adapters/input/web/bill.rs @@ -0,0 +1,168 @@ +// SPDX-FileCopyrightText: 2024 Aravinth Manivannan +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +use actix_identity::Identity; +use actix_web::{get, http::header::ContentType, post, web, HttpRequest, HttpResponse, Responder}; +use serde::{Deserialize, Serialize}; +use url::Url; +use uuid::Uuid; + +use super::errors::*; +use super::types; +use crate::billing::domain::add_store_command::AddStoreCommandBuilder; +use crate::billing::domain::{add_bill_command::*, bill_updated_event, commands::BillingCommand}; +use crate::utils::uuid::WebGetUUIDInterfaceObj; + +pub fn services(cfg: &mut web::ServiceConfig) { + cfg.service(add_bill_submit_handler); + cfg.service(add_bill_page_handler); + cfg.service(add_store_page_handler); + cfg.service(add_store_submit_handler); +} + +#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] +struct WebAddBillPayload { + store_id: Uuid, +} + +#[allow(clippy::too_many_arguments)] +#[get("/billing/store/add")] +#[tracing::instrument(name = "add store page handler", skip())] +//async fn add_bill_page_handler(_: Identity) -> WebJsonRepsonse { +async fn add_store_page_handler() -> WebJsonRepsonse { + let page = r#" + + + + + + Document + + +
+ +
+ + + + "#; + + Ok(HttpResponse::Ok() + .insert_header(ContentType::html()) + .body(page)) +} + +const UUID: Uuid = uuid::uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8"); + +#[allow(clippy::too_many_arguments)] +#[post("/billing/store/add")] +#[tracing::instrument( + name = "add store handler", + skip(billing_store_cqrs_exec, billing_store_cqrs_view, uuid_generator) +)] +async fn add_store_submit_handler( + billing_store_cqrs_exec: types::WebBillingStoreCqrsExec, + billing_store_cqrs_view: types::WebBillingStoreCqrsView, + uuid_generator: WebGetUUIDInterfaceObj, + req: HttpRequest, + // id: Identity, +) -> WebJsonRepsonse { + // let user_id = Uuid::parse_str(&id.id().unwrap()).unwrap(); + let user_id = UUID; + + let store_uuid = UUID; + let store_uuid_str = store_uuid.to_string(); + let cmd = AddStoreCommandBuilder::default() + .name("foo".into()) + .owner(user_id) + .store_id(UUID) + .address(None) + .build() + .unwrap(); + + billing_store_cqrs_exec + .execute(&store_uuid_str, BillingCommand::AddStore(cmd)) + .await + .unwrap(); + + let store = billing_store_cqrs_view + .load(&store_uuid_str) + .await + .unwrap() + .unwrap(); + + Ok(HttpResponse::Ok().json(store)) +} + +#[allow(clippy::too_many_arguments)] +#[get("/billing/bill/add")] +#[tracing::instrument(name = "add bill page handler", skip())] +//async fn add_bill_page_handler(_: Identity) -> WebJsonRepsonse { +async fn add_bill_page_handler() -> WebJsonRepsonse { + let page = r#" + + + + + + Document + + + Token Refreshed +
+ + +
+ + + + "#; + + Ok(HttpResponse::Ok() + .insert_header(ContentType::html()) + .body(page)) +} + +#[allow(clippy::too_many_arguments)] +#[post("/billing/bill/add")] +#[tracing::instrument( + name = "add bill handler", + skip(billing_bill_cqrs_exec, billing_bill_cqrs_view,) +)] +async fn add_bill_submit_handler( + billing_bill_cqrs_exec: types::WebBillingBillCqrsExec, + billing_bill_cqrs_view: types::WebBillingBillCqrsView, + req: HttpRequest, + // id: Identity, + payload: web::Form, +) -> WebJsonRepsonse { + // let user_id = Uuid::parse_str(&id.id().unwrap()).unwrap(); + let user_id = Uuid::new_v4(); + + let bill_uuid = Uuid::new_v4(); + let bill_uuid_str = bill_uuid.to_string(); + let cmd = AddBillCommandBuilder::default() + .adding_by(user_id) + .bill_id(bill_uuid) + .store_id(payload.store_id) + .build() + .unwrap(); + + billing_bill_cqrs_exec + .execute(&bill_uuid_str, BillingCommand::AddBill(cmd)) + .await + .unwrap(); + + let bill = billing_bill_cqrs_view + .load(&bill_uuid_str) + .await + .unwrap() + .unwrap(); + + Ok(HttpResponse::Ok().json(bill)) +} diff --git a/src/billing/adapters/input/web/errors.rs b/src/billing/adapters/input/web/errors.rs new file mode 100644 index 0000000..927be55 --- /dev/null +++ b/src/billing/adapters/input/web/errors.rs @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: 2024 Aravinth Manivannan +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +use actix_web::http::StatusCode; +use actix_web::{HttpResponse, ResponseError}; +use derive_more::Display; +use serde::{Deserialize, Serialize}; + +use crate::billing::application::services::errors::*; + +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +struct ErrorResponse { + error: String, +} + +impl From for ErrorResponse { + fn from(value: WebError) -> Self { + ErrorResponse { + error: serde_json::to_string(&value).unwrap_or_else(|_| { + log::error!("Unable to serialize error"); + "Unable to serialize error".into() + }), + } + } +} + +#[derive(Debug, Display, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] +pub enum WebError { + InternalError, + BillNotFound, + StoreNotFound, + LineItemNotFound, + BadRequest, +} + +impl From for WebError { + fn from(v: BillingError) -> Self { + match v { + BillingError::BillIDNotFound => Self::BillNotFound, + BillingError::InternalError => Self::InternalError, + BillingError::DuplicateStoreName => Self::InternalError, + BillingError::DuplicateBillID => Self::InternalError, + BillingError::DuplicateLineItemID => Self::InternalError, + BillingError::DuplicateStoreID => Self::InternalError, + BillingError::StoreIDNotFound => Self::StoreNotFound, + BillingError::LineItemIDNotFound => Self::LineItemNotFound, + } + } +} + +impl ResponseError for WebError { + fn status_code(&self) -> StatusCode { + match self { + Self::InternalError => StatusCode::INTERNAL_SERVER_ERROR, + Self::StoreNotFound => StatusCode::NOT_FOUND, + Self::LineItemNotFound => StatusCode::NOT_FOUND, + Self::BillNotFound => StatusCode::NOT_FOUND, + Self::BadRequest => StatusCode::BAD_REQUEST, + } + } + + fn error_response(&self) -> actix_web::HttpResponse { + let e: ErrorResponse = self.clone().into(); + match self { + Self::InternalError => HttpResponse::InternalServerError().json(e), + Self::StoreNotFound => HttpResponse::NotFound().json(e), + Self::LineItemNotFound => HttpResponse::NotFound().json(e), + Self::BillNotFound => HttpResponse::BadRequest().json(e), + Self::BadRequest => HttpResponse::BadRequest().json(e), + } + } +} + +pub type WebJsonRepsonse = Result; diff --git a/src/billing/adapters/input/web/mod.rs b/src/billing/adapters/input/web/mod.rs new file mode 100644 index 0000000..f255fe7 --- /dev/null +++ b/src/billing/adapters/input/web/mod.rs @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2024 Aravinth Manivannan +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +use std::sync::Arc; + +use actix_web::web; + +use crate::billing::adapters::types; + +mod bill; +mod errors; +mod routes; + +pub use errors::WebJsonRepsonse; + +pub use routes::RoutesRepository; + +pub fn load_ctx() -> impl FnOnce(&mut web::ServiceConfig) { + let routes = types::WebBillingRoutesRepository::new(Arc::new(RoutesRepository::default())); + + let f = move |cfg: &mut web::ServiceConfig| { + cfg.app_data(routes); + cfg.configure(bill::services); + }; + + Box::new(f) +} diff --git a/src/billing/adapters/input/web/routes.rs b/src/billing/adapters/input/web/routes.rs new file mode 100644 index 0000000..c680d86 --- /dev/null +++ b/src/billing/adapters/input/web/routes.rs @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: 2024 Aravinth Manivannan +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +use url::Url; +use uuid::Uuid; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct RoutesRepository { + add_bill: String, + update_bill: String, + delete_bill: String, + compute_total_price_for_bill: String, + + add_line_item: String, + update_line_item: String, + delete_line_item: String, +} + +impl Default for RoutesRepository { + fn default() -> Self { + Self { + add_bill: "/billing/bill/add".into(), + update_bill: "/billing/bill/{bill_uuid}".into(), + delete_bill: "/billing/bill/{bill_uuid}".into(), + compute_total_price_for_bill: "/billing/bill/{bill_uuid}/compute-total".into(), + + add_line_item: "/billing/bill/{bill_id}/line_item/add".into(), + update_line_item: "/billing/bill/{bill_id}/line_item/{line_item_uuid}".into(), + delete_line_item: "/billing/bill/{bill_id}/line_item/{line_item_uuid}".into(), + } + } +} + +impl RoutesRepository { + pub fn update_bill(&self, bill_id: Uuid) -> String { + self.update_bill + .replace("{bill_uuid}", &bill_id.to_string()) + } + + pub fn delete_bill(&self, bill_id: Uuid) -> String { + self.delete_bill + .replace("{bill_uuid}", &bill_id.to_string()) + } + + pub fn compute_total_price_for_bill(&self, bill_id: Uuid) -> String { + self.compute_total_price_for_bill + .replace("{bill_uuid}", &bill_id.to_string()) + } + + pub fn add_line_item(&self, bill_id: Uuid) -> String { + self.add_line_item + .replace("{bill_uuid}", &bill_id.to_string()) + } + + pub fn update_line_item(&self, bill_id: Uuid, line_item_uuid: Uuid) -> String { + self.update_line_item + .replace("{bill_uuid}", &bill_id.to_string()) + .replace("{line_item_uuid}", &line_item_uuid.to_string()) + } + + pub fn delete_line_item(&self, bill_id: Uuid, line_item_uuid: Uuid) -> String { + self.delete_line_item + .replace("{bill_uuid}", &bill_id.to_string()) + .replace("{line_item_uuid}", &line_item_uuid.to_string()) + } +} diff --git a/src/billing/adapters/output/mod.rs b/src/billing/adapters/output/mod.rs index f895176..bb9a6b6 100644 --- a/src/billing/adapters/output/mod.rs +++ b/src/billing/adapters/output/mod.rs @@ -1,4 +1,4 @@ // SPDX-FileCopyrightText: 2024 Aravinth Manivannan // // SPDX-License-Identifier: AGPL-3.0-or-later -mod db; +pub(crate) mod db; diff --git a/src/billing/mod.rs b/src/billing/mod.rs index 7272406..10dac6e 100644 --- a/src/billing/mod.rs +++ b/src/billing/mod.rs @@ -2,6 +2,6 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -mod adapters; +pub(crate) mod adapters; mod application; mod domain;