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-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
// 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-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
// 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
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
mod adapters;
|
pub(crate) mod adapters;
|
||||||
mod application;
|
mod application;
|
||||||
mod domain;
|
mod domain;
|
||||||
|
|
Loading…
Reference in a new issue