From 41c46577411da5d0c20b8047032ee382eb613ddc Mon Sep 17 00:00:00 2001 From: Aravinth Manivannan Date: Thu, 9 Jan 2025 23:05:24 +0530 Subject: [PATCH] feat: bootstrap identity web adapter --- src/identity/adapters/input/mod.rs | 1 + src/identity/adapters/input/web/errors.rs | 134 ++++++++++++++++++++++ src/identity/adapters/input/web/mod.rs | 28 +++++ src/identity/adapters/input/web/routes.rs | 86 ++++++++++++++ src/identity/adapters/mod.rs | 4 +- 5 files changed, 250 insertions(+), 3 deletions(-) create mode 100644 src/identity/adapters/input/web/errors.rs create mode 100644 src/identity/adapters/input/web/mod.rs create mode 100644 src/identity/adapters/input/web/routes.rs diff --git a/src/identity/adapters/input/mod.rs b/src/identity/adapters/input/mod.rs index 56f60de..810dc4e 100644 --- a/src/identity/adapters/input/mod.rs +++ b/src/identity/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/identity/adapters/input/web/errors.rs b/src/identity/adapters/input/web/errors.rs new file mode 100644 index 0000000..8321ee0 --- /dev/null +++ b/src/identity/adapters/input/web/errors.rs @@ -0,0 +1,134 @@ +// 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::identity::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, + BadRequest, + + DuplicateUsername, + VerificationOTPNotFound, + VerificationSecretNotFound, + PhoneNumberVerificationFailed, + LoginOTPNotFound, + LoginFailed, + DuplicateEmail, + DuplicateEmployeeID, + DuplicatePhoneNumber, + DuplicateVerificationOTP, + PhoneNumberNotFound, + EmployeeNotFound, + InviteNotFound, + StoreNotFound, + DuplicateStoreName, + StoreIDNotFound, + DuplicateStoreID, +} + +impl From for WebError { + fn from(v: IdentityError) -> Self { + match v { + IdentityError::InternalError => Self::InternalError, + IdentityError::StoreIDNotFound => Self::StoreNotFound, + IdentityError::DuplicateUsername => Self::DuplicateUsername, + IdentityError::VerificationOTPNotFound => Self::VerificationOTPNotFound, + IdentityError::VerificationSecretNotFound => Self::VerificationSecretNotFound, + IdentityError::PhoneNumberVerificationFailed => Self::PhoneNumberNotFound, + IdentityError::LoginOTPNotFound => Self::LoginOTPNotFound, + IdentityError::LoginFailed => Self::LoginFailed, + IdentityError::DuplicateEmail => Self::DuplicateEmail, + IdentityError::DuplicateEmployeeID => Self::DuplicateEmployeeID, + IdentityError::DuplicatePhoneNumber => Self::DuplicatePhoneNumber, + IdentityError::DuplicateVerificationOTP => Self::DuplicateVerificationOTP, + IdentityError::PhoneNumberNotFound => Self::PhoneNumberNotFound, + IdentityError::EmployeeNotFound => Self::EmployeeNotFound, + IdentityError::InviteNotFound => Self::InviteNotFound, + IdentityError::StoreNotFound => Self::StoreNotFound, + IdentityError::DuplicateStoreName => Self::DuplicateStoreName, + IdentityError::StoreIDNotFound => Self::StoreIDNotFound, + IdentityError::DuplicateStoreID => Self::DuplicateStoreID, + } + } +} + +impl ResponseError for WebError { + fn status_code(&self) -> StatusCode { + match self { + Self::InternalError => StatusCode::INTERNAL_SERVER_ERROR, + Self::BadRequest => StatusCode::BAD_REQUEST, + + Self::StoreIDNotFound => StatusCode::NOT_FOUND, + Self::DuplicateUsername => StatusCode::BAD_REQUEST, + Self::VerificationOTPNotFound => StatusCode::UNAUTHORIZED, + Self::VerificationSecretNotFound => StatusCode::UNAUTHORIZED, + Self::PhoneNumberVerificationFailed => StatusCode::UNAUTHORIZED, + Self::LoginOTPNotFound => StatusCode::UNAUTHORIZED, + Self::LoginFailed => StatusCode::UNAUTHORIZED, + Self::DuplicateEmail => StatusCode::BAD_REQUEST, + Self::DuplicateEmployeeID => StatusCode::INTERNAL_SERVER_ERROR, + Self::DuplicatePhoneNumber => StatusCode::BAD_REQUEST, + Self::DuplicateVerificationOTP => StatusCode::INTERNAL_SERVER_ERROR, + Self::PhoneNumberNotFound => StatusCode::UNAUTHORIZED, // (?) + Self::EmployeeNotFound => StatusCode::NOT_FOUND, + Self::InviteNotFound => StatusCode::NOT_FOUND, + Self::StoreNotFound => StatusCode::NOT_FOUND, + Self::DuplicateStoreName => StatusCode::BAD_REQUEST, + Self::StoreIDNotFound => StatusCode::NOT_FOUND, + Self::DuplicateStoreID => StatusCode::INTERNAL_SERVER_ERROR, + } + } + + fn error_response(&self) -> actix_web::HttpResponse { + let e: ErrorResponse = self.clone().into(); + match self { + Self::InternalError => HttpResponse::InternalServerError().json(e), + Self::BadRequest => HttpResponse::BadRequest().json(e), + + Self::StoreNotFound => HttpResponse::NotFound().json(e), + Self::StoreIDNotFound => HttpResponse::NotFound().json(e), + Self::DuplicateUsername => HttpResponse::BadRequest().json(e), + Self::VerificationOTPNotFound => HttpResponse::Unauthorized().json(e), + Self::VerificationSecretNotFound => HttpResponse::Unauthorized().json(e), + Self::PhoneNumberVerificationFailed => HttpResponse::Unauthorized().json(e), + Self::LoginOTPNotFound => HttpResponse::Unauthorized().json(e), + Self::LoginFailed => HttpResponse::Unauthorized().json(e), + Self::DuplicateEmail => HttpResponse::BadRequest().json(e), + Self::DuplicateEmployeeID => HttpResponse::InternalServerError().json(e), + Self::DuplicatePhoneNumber => HttpResponse::BadRequest().json(e), + Self::DuplicateVerificationOTP => HttpResponse::InternalServerError().json(e), + Self::PhoneNumberNotFound => HttpResponse::Unauthorized().json(e), // (?) + Self::EmployeeNotFound => HttpResponse::NotFound().json(e), + Self::InviteNotFound => HttpResponse::NotFound().json(e), + Self::StoreNotFound => HttpResponse::NotFound().json(e), + Self::DuplicateStoreName => HttpResponse::BadRequest().json(e), + Self::StoreIDNotFound => HttpResponse::NotFound().json(e), + Self::DuplicateStoreID => HttpResponse::InternalServerError().json(e), + } + } +} + +pub type WebJsonRepsonse = Result; diff --git a/src/identity/adapters/input/web/mod.rs b/src/identity/adapters/input/web/mod.rs new file mode 100644 index 0000000..678fe25 --- /dev/null +++ b/src/identity/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::identity::adapters::types; + +mod errors; +mod owner; +mod routes; + +pub use errors::WebJsonRepsonse; + +pub use routes::RoutesRepository; + +pub fn load_ctx() -> impl FnOnce(&mut web::ServiceConfig) { + let routes = types::WebIdentityRoutesRepository::new(Arc::new(RoutesRepository::default())); + + let f = move |cfg: &mut web::ServiceConfig| { + cfg.app_data(routes); + cfg.configure(owner::services); + }; + + Box::new(f) +} diff --git a/src/identity/adapters/input/web/routes.rs b/src/identity/adapters/input/web/routes.rs new file mode 100644 index 0000000..7134c55 --- /dev/null +++ b/src/identity/adapters/input/web/routes.rs @@ -0,0 +1,86 @@ +// 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 { + owner_login: String, + owner_register: String, + owner_delete: String, + owner_verify_email: String, + owner_resend_verification_email: String, + // owner_set_admin: String, + owner_update_email: String, + owner_change_password: String, + + owner_add_store: String, + owner_update_store: String, + + employee_exit_organization: String, + employee_login: String, + employee_register: String, + employee_resend_login_otp: String, + employee_resend_verification_otp: String, + employee_verify_phone_number: String, +} + +impl Default for RoutesRepository { + fn default() -> Self { + Self { + owner_login: "/owner/login".into(), + owner_register: "/owner/register".into(), + owner_delete: "/owner/user/delete".into(), + owner_verify_email: "/owner/user/verify/email".into(), + owner_resend_verification_email: "/owner/user/verify/email/resend".into(), + owner_update_email: "/owner/user/email/update".into(), + owner_change_password: "/owner/password/change".into(), + //owner_set_admin: "/owner/user/promote/admin".into(), + owner_add_store: "/owner/store".into(), + owner_update_store: "/owner/store/update".into(), + + employee_login: "/employee/login".into(), + employee_register: "/employee/register".into(), + employee_resend_login_otp: "/employee/login/resend_otp".into(), + employee_resend_verification_otp: "/employee/user/phone/verify/resend_otp".into(), + employee_verify_phone_number: "/employee/user/phone/verify".into(), + employee_exit_organization: "/employee/organization/exit".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/identity/adapters/mod.rs b/src/identity/adapters/mod.rs index a337b8f..e55f278 100644 --- a/src/identity/adapters/mod.rs +++ b/src/identity/adapters/mod.rs @@ -10,9 +10,7 @@ use postgres_es::PostgresCqrs; use sqlx::postgres::PgPool; use crate::identity::{ - application::{ - services::{IdentityServices, IdentityServicesObj}, - }, + application::services::{IdentityServices, IdentityServicesObj}, domain::{aggregate::User, employee_aggregate::Employee, store_aggregate::Store}, }; use crate::settings::Settings;