refactor: migrate to clean architecture with trait-based DI (v0.2.0)

Complete architectural overhaul across all 12 crates:

- Replace validator crate with zod-rs for all DTO validation
- Replace manual pagination with paginator-rs/paginator-sea-orm
- Migrate all modules (iam, cms, gacha, dimentorin) to clean architecture:
  domain → application → infrastructure layers
- Introduce trait-based DI (Arc<dyn Trait>) at every layer for repositories and services
- Delete all v1/ legacy SurrealDB-era code across every crate
- Replace opaque response helpers with typed IntoResponse structs (ApiSuccess, ApiCreated, ApiPaginated, ApiMessage)
- Remove dual_mode_repository, migration_validation_errors, validator.rs dead code
- Zero cargo clippy warnings; release build clean

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
maulanasdqn
2026-04-02 13:39:52 +07:00
co-authored by Claude Sonnet 4.6
parent 1b3366d735
commit e432a1a743
379 changed files with 9013 additions and 30532 deletions
@@ -1,12 +1,12 @@
use axum::{
Extension, extract::Request, http::StatusCode, middleware::Next,
response::Response,
response::{IntoResponse, Response},
};
use imphnen_libs::{AppState, jsonwebtoken::decode_access_token};
use axum_extra::headers::{authorization::Bearer, Authorization, HeaderMapExt};
use std::convert::Infallible;
use uuid::Uuid;
use imphnen_utils::response_format::common_response;
use imphnen_utils::response_format::ApiMessage;
pub async fn auth_middleware(
Extension(state): Extension<AppState>,
@@ -17,20 +17,20 @@ pub async fn auth_middleware(
.headers()
.typed_get::<Authorization<Bearer>>() {
Some(header) => header,
None => return Ok(common_response(
None => return Ok(ApiMessage::new(
StatusCode::UNAUTHORIZED,
"Invalid or missing authorization token",
)),
).into_response()),
};
let token = auth_header.token();
let claims = match decode_access_token(token) {
Ok(token_data) => token_data.claims,
Err(_) => return Ok(common_response(
Err(_) => return Ok(ApiMessage::new(
StatusCode::UNAUTHORIZED,
"Invalid or expired token",
)),
).into_response()),
};
let user_id = claims.user_id.clone();
@@ -38,17 +38,17 @@ pub async fn auth_middleware(
// Validate UUID format
let user_uuid = match Uuid::parse_str(&user_id) {
Ok(uuid) => uuid,
Err(_) => return Ok(common_response(
Err(_) => return Ok(ApiMessage::new(
StatusCode::UNAUTHORIZED,
"Invalid user identifier format",
)),
).into_response()),
};
// Use UserLookupService to fetch full user details including roles/permissions
// This ensures consistency and populates the DTO expected by controllers
let user_info = match state.user_lookup_service.get_user_by_id(user_uuid, &state).await {
Ok(info) => info,
Err(_) => return Ok(common_response(StatusCode::UNAUTHORIZED, "User not found or inactive")),
Err(_) => return Ok(ApiMessage::new(StatusCode::UNAUTHORIZED, "User not found or inactive").into_response()),
};
// Insert the Model (reconstructed or fetched? Wait, UserLookupService returns ExtendedUserInfo)
@@ -5,7 +5,8 @@ use axum::{
use futures::future::BoxFuture;
use imphnen_entities::PermissionsEnum;
use imphnen_libs::{AppState, services::ExtendedUserInfo};
use imphnen_utils::response_format::common_response;
use imphnen_utils::response_format::ApiMessage;
use axum::response::IntoResponse;
use imphnen_utils::{extract_email, extract_email_async};
use std::task::{Context, Poll};
use tower::{Layer, Service};
@@ -77,19 +78,19 @@ where
// Extract user email from authorization headers
let email = extract_user_email(headers).await
.ok_or_else(|| {
common_response(
ApiMessage::new(
StatusCode::UNAUTHORIZED,
"Invalid or missing authorization token",
)
).into_response()
})?;
// Get user data with permissions from user lookup service
let user = app_state.user_lookup_service.get_user_by_email(&email, &app_state).await
.map_err(|_| {
common_response(
ApiMessage::new(
StatusCode::UNAUTHORIZED,
"User session expired or not found",
)
).into_response()
})?;
// Extract user permissions from role
@@ -100,10 +101,10 @@ where
// Check if user has required permissions
if !has_required_permissions(&user_permissions, &permissions) {
return Err(common_response(
return Err(ApiMessage::new(
StatusCode::FORBIDDEN,
"You don't have the required permissions",
));
).into_response());
}
inner.call(req).await
@@ -174,27 +175,27 @@ pub async fn check_permissions(
) -> Result<(), Response<Body>> {
let email = extract_user_email(headers).await
.ok_or_else(|| {
common_response(
ApiMessage::new(
StatusCode::UNAUTHORIZED,
"Invalid or missing authorization token",
)
).into_response()
})?;
let user = app_state.user_lookup_service.get_user_by_email(&email, app_state).await
.map_err(|_| {
common_response(
ApiMessage::new(
StatusCode::UNAUTHORIZED,
"User session expired or not found",
)
).into_response()
})?;
let user_permissions = extract_user_permissions(&user);
if !has_required_permissions(&user_permissions, &required_permissions) {
return Err(common_response(
return Err(ApiMessage::new(
StatusCode::FORBIDDEN,
"You don't have the required permissions",
));
).into_response());
}
Ok(())