feat: bootstrap identity web adapter

This commit is contained in:
Aravinth Manivannan 2025-01-09 23:05:24 +05:30
parent 2a85f88e04
commit 41c4657741
Signed by: realaravinth
GPG key ID: F8F50389936984FF
5 changed files with 250 additions and 3 deletions

View file

@ -1,3 +1,4 @@
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
pub mod web;

View file

@ -0,0 +1,134 @@
// 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::identity::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,
BadRequest,
DuplicateUsername,
VerificationOTPNotFound,
VerificationSecretNotFound,
PhoneNumberVerificationFailed,
LoginOTPNotFound,
LoginFailed,
DuplicateEmail,
DuplicateEmployeeID,
DuplicatePhoneNumber,
DuplicateVerificationOTP,
PhoneNumberNotFound,
EmployeeNotFound,
InviteNotFound,
StoreNotFound,
DuplicateStoreName,
StoreIDNotFound,
DuplicateStoreID,
}
impl From<IdentityError> 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<V> = Result<V, WebError>;

View 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::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)
}

View file

@ -0,0 +1,86 @@
// 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 {
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())
// }
}

View file

@ -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;