feat: bootstrap add bill web interface
This commit is contained in:
parent
f8870594ce
commit
92a22100be
7 changed files with 341 additions and 2 deletions
|
@ -1,3 +1,4 @@
|
|||
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
pub mod web;
|
||||
|
|
168
src/billing/adapters/input/web/bill.rs
Normal file
168
src/billing/adapters/input/web/bill.rs
Normal file
|
@ -0,0 +1,168 @@
|
|||
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
//
|
||||
// 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<impl Responder> {
|
||||
async fn add_store_page_handler() -> WebJsonRepsonse<impl Responder> {
|
||||
let page = r#"
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="/billing/store/add" method="post">
|
||||
<button type="submit">
|
||||
Create Store
|
||||
</button>
|
||||
</form>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
"#;
|
||||
|
||||
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<impl Responder> {
|
||||
// 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<impl Responder> {
|
||||
async fn add_bill_page_handler() -> WebJsonRepsonse<impl Responder> {
|
||||
let page = r#"
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
Token Refreshed
|
||||
<form action="/billing/bill/add" method="post">
|
||||
<label> Store ID <input type="text" name="store_id" id="store_id" /></label>
|
||||
<button type="submit">
|
||||
Refresh Token
|
||||
</button>
|
||||
</form>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
"#;
|
||||
|
||||
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<WebAddBillPayload>,
|
||||
) -> WebJsonRepsonse<impl Responder> {
|
||||
// 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))
|
||||
}
|
75
src/billing/adapters/input/web/errors.rs
Normal file
75
src/billing/adapters/input/web/errors.rs
Normal file
|
@ -0,0 +1,75 @@
|
|||
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
//
|
||||
// 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<WebError> 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<BillingError> 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<V> = Result<V, WebError>;
|
28
src/billing/adapters/input/web/mod.rs
Normal file
28
src/billing/adapters/input/web/mod.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
//
|
||||
// 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)
|
||||
}
|
67
src/billing/adapters/input/web/routes.rs
Normal file
67
src/billing/adapters/input/web/routes.rs
Normal file
|
@ -0,0 +1,67 @@
|
|||
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
//
|
||||
// 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())
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
mod db;
|
||||
pub(crate) mod db;
|
||||
|
|
|
@ -2,6 +2,6 @@
|
|||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
mod adapters;
|
||||
pub(crate) mod adapters;
|
||||
mod application;
|
||||
mod domain;
|
||||
|
|
Loading…
Reference in a new issue