chore: cleaning code

This commit is contained in:
Maulana Sodiqin
2025-05-20 00:26:24 +07:00
parent e48507901e
commit d97095a38d
16 changed files with 139 additions and 251 deletions
+12 -12
View File
@@ -25,8 +25,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
( (
"0269ed71-0ae0-4c43-ad29-e3d861d8f9a0", "0269ed71-0ae0-4c43-ad29-e3d861d8f9a0",
"Create Permissions", "Create Permissions",
None, Some("2025-01-29T05:11:01.265+00"),
None, Some("2025-01-29T05:11:01.265001+00"),
), ),
( (
"299cb4d5-6556-4cc9-b6c1-32e6d31e0f9b", "299cb4d5-6556-4cc9-b6c1-32e6d31e0f9b",
@@ -37,20 +37,20 @@ async fn main() -> Result<(), Box<dyn Error>> {
( (
"319ee593-ff0a-4f29-bbaf-9feb3174a3a2", "319ee593-ff0a-4f29-bbaf-9feb3174a3a2",
"Create Roles", "Create Roles",
None, Some("2025-01-29T05:11:01.265+00"),
None, Some("2025-01-29T05:11:01.265001+00"),
), ),
( (
"319ee593-ff0a-4f29-bbaf-9feb3174a3a6", "319ee593-ff0a-4f29-bbaf-9feb3174a3a6",
"Read Detail Users", "Read Detail Users",
None, Some("2025-01-29T05:11:01.265+00"),
None, Some("2025-01-29T05:11:01.265001+00"),
), ),
( (
"35b0d992-65c8-4b62-b030-e6e0320e4048", "35b0d992-65c8-4b62-b030-e6e0320e4048",
"Delete Roles", "Delete Roles",
None, Some("2025-01-29T05:34:40.621554+00"),
None, Some("2025-01-29T05:34:40.621555+00"),
), ),
( (
"4da8b434-89f9-4d91-85ae-eebd63cdbeda", "4da8b434-89f9-4d91-85ae-eebd63cdbeda",
@@ -91,8 +91,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
( (
"9164ca6e-c7e3-4238-a15f-f36ab9577e7e", "9164ca6e-c7e3-4238-a15f-f36ab9577e7e",
"Read List Roles", "Read List Roles",
None, Some("2025-01-29T05:34:40.621554+00"),
None, Some("2025-01-29T05:34:40.621555+00"),
), ),
( (
"96df0689-2ae9-4894-bf00-837c19415e5c", "96df0689-2ae9-4894-bf00-837c19415e5c",
@@ -109,8 +109,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
( (
"a00d5608-4c48-4542-845c-dfe004687022", "a00d5608-4c48-4542-845c-dfe004687022",
"Update Roles", "Update Roles",
None, Some("2025-01-29T05:34:40.621554+00"),
None, Some("2025-01-29T05:34:40.621555+00"),
), ),
( (
"b2dc3928-86ba-4c59-a03d-0b57d5183ebc", "b2dc3928-86ba-4c59-a03d-0b57d5183ebc",
@@ -28,6 +28,8 @@ impl<'a> GachaItemRepository<'a> {
&meta, &meta,
conditions, conditions,
None, None,
"name",
None,
) )
.await .await
} }
+5 -6
View File
@@ -1,6 +1,5 @@
use imphnen_iam::{ use imphnen_iam::{
auth, permissions, roles, users, AuthLoginRequestDto, AuthLoginResponsetDto, AuthResendOtpRequestDto, AuthVerifyEmailRequestDto, auth, permissions, roles, users, AuthLoginRequestDto, AuthLoginResponsetDto, AuthNewPasswordRequestDto, AuthRefreshTokenRequestDto, AuthResendOtpRequestDto, AuthVerifyEmailRequestDto, MessageResponseDto, MetaRequestDto, MetaResponseDto, PermissionsItemDto, PermissionsRequestDto, ResponseListSuccessDto, ResponseSuccessDto, RolesDetailItemDto, RolesListItemDto, RolesRequestCreateDto, RolesRequestUpdateDto, TokenDto, UsersCreateRequestDto, UsersDetailItemDto, UsersListItemDto, UsersUpdateRequestDto
AuthNewPasswordRequestDto, AuthRefreshTokenRequestDto, MessageResponseDto, MetaRequestDto, MetaResponseDto, PermissionsItemDto, PermissionsRequestDto, ResponseListSuccessDto, ResponseSuccessDto, RolesItemDto, RolesRequestCreateDto, RolesRequestUpdateDto, TokenDto, UsersCreateRequestDto, UsersDetailItemDto, UsersItemDto, UsersListItemDto, UsersUpdateRequestDto
}; };
use utoipa::{ use utoipa::{
openapi::security::{Http, HttpAuthScheme, SecurityScheme}, openapi::security::{Http, HttpAuthScheme, SecurityScheme},
@@ -48,18 +47,18 @@ use utoipa::{
AuthNewPasswordRequestDto, AuthNewPasswordRequestDto,
AuthRefreshTokenRequestDto, AuthRefreshTokenRequestDto,
ResponseSuccessDto<TokenDto>, ResponseSuccessDto<TokenDto>,
RolesItemDto, RolesListItemDto,
RolesRequestCreateDto, RolesRequestCreateDto,
RolesRequestUpdateDto, RolesRequestUpdateDto,
PermissionsRequestDto, PermissionsRequestDto,
PermissionsItemDto, PermissionsItemDto,
UsersItemDto, UsersDetailItemDto,
UsersListItemDto, UsersListItemDto,
UsersUpdateRequestDto, UsersUpdateRequestDto,
UsersCreateRequestDto, UsersCreateRequestDto,
ResponseSuccessDto<AuthLoginResponsetDto>, ResponseSuccessDto<AuthLoginResponsetDto>,
ResponseListSuccessDto<Vec<RolesItemDto>>, ResponseListSuccessDto<Vec<RolesListItemDto>>,
ResponseSuccessDto<RolesItemDto>, ResponseSuccessDto<RolesDetailItemDto>,
ResponseListSuccessDto<Vec<UsersListItemDto>>, ResponseListSuccessDto<Vec<UsersListItemDto>>,
ResponseSuccessDto<UsersDetailItemDto>, ResponseSuccessDto<UsersDetailItemDto>,
ResponseListSuccessDto<Vec<PermissionsItemDto>>, ResponseListSuccessDto<Vec<PermissionsItemDto>>,
+7 -37
View File
@@ -1,12 +1,16 @@
use crate::RolesItemDto;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use regex::Regex; use regex::Regex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use utoipa::ToSchema; use utoipa::ToSchema;
use validator::{Validate, ValidationError}; use validator::{Validate, ValidationError};
use crate::UsersDetailItemDto;
lazy_static! { lazy_static! {
static ref PASSWORD_REGEX: Regex = Regex::new(r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$").unwrap(); static ref PASSWORD_REGEX: Regex = Regex::new(
r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$"
)
.unwrap();
} }
fn validate_password_complexity(password: &str) -> Result<(), ValidationError> { fn validate_password_complexity(password: &str) -> Result<(), ValidationError> {
@@ -33,23 +37,10 @@ pub struct AuthLoginRequestDto {
pub password: String, pub password: String,
} }
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct AuthUserItemDto {
pub id: String,
pub role: RolesItemDto,
pub fullname: String,
pub email: String,
pub avatar: Option<String>,
pub phone_number: String,
pub is_active: bool,
pub gender: Option<String>,
pub birthdate: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct AuthLoginResponsetDto { pub struct AuthLoginResponsetDto {
pub token: TokenDto, pub token: TokenDto,
pub user: AuthUserItemDto, pub user: UsersDetailItemDto,
} }
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
@@ -77,28 +68,7 @@ pub struct AuthRegisterRequestDto {
#[validate(length(min = 2, message = "Fullname at least have 2 character"))] #[validate(length(min = 2, message = "Fullname at least have 2 character"))]
pub fullname: String, pub fullname: String,
#[validate(length(min = 1, message = "Student type is required"))] #[validate(length(min = 1, message = "Student type is required"))]
pub student_type: String,
#[validate(length(
min = 10,
message = "Phone number at least have 10 character"
))]
pub phone_number: String, pub phone_number: String,
#[validate(length(
max = 4,
message = "Referal code cannot be more than 4 character"
))]
pub referral_code: Option<String>,
pub referred_by: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
pub struct AuthActiveInactiveRequestDto {
pub is_active: bool,
#[validate(
length(min = 1, message = "Email cannot be empty"),
email(message = "Email not valid")
)]
pub email: String,
} }
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)] #[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
+8 -8
View File
@@ -1,6 +1,6 @@
use super::AuthOtpSchema; use super::AuthOtpSchema;
use crate::{make_thing, AppState, ResourceEnum, UsersItemDtoRaw}; use crate::{AppState, ResourceEnum, UsersDetailQueryDto, make_thing};
use anyhow::{anyhow, bail, Result}; use anyhow::{Result, anyhow, bail};
use chrono::{Duration, Utc}; use chrono::{Duration, Utc};
pub struct AuthRepository<'a> { pub struct AuthRepository<'a> {
@@ -12,7 +12,7 @@ impl<'a> AuthRepository<'a> {
Self { state } Self { state }
} }
pub async fn query_store_user(&self, user: UsersItemDtoRaw) -> Result<String> { pub async fn query_store_user(&self, user: UsersDetailQueryDto) -> Result<String> {
if user.email.trim().is_empty() { if user.email.trim().is_empty() {
bail!("Email is required"); bail!("Email is required");
} }
@@ -22,11 +22,11 @@ impl<'a> AuthRepository<'a> {
let _ = self let _ = self
.state .state
.surrealdb_mem .surrealdb_mem
.delete::<Option<UsersItemDtoRaw>>((table.clone(), user_id.clone())) .delete::<Option<UsersDetailQueryDto>>((table.clone(), user_id.clone()))
.await?; .await?;
let mut user_to_store = user.clone(); let mut user_to_store = user.clone();
user_to_store.id = id.clone(); user_to_store.id = id.clone();
let record: Option<UsersItemDtoRaw> = self let record: Option<UsersDetailQueryDto> = self
.state .state
.surrealdb_mem .surrealdb_mem
.create((table, user_id)) .create((table, user_id))
@@ -41,8 +41,8 @@ impl<'a> AuthRepository<'a> {
pub async fn query_get_stored_user( pub async fn query_get_stored_user(
&self, &self,
email: String, email: String,
) -> Result<UsersItemDtoRaw> { ) -> Result<UsersDetailQueryDto> {
let user: Option<UsersItemDtoRaw> = self let user: Option<UsersDetailQueryDto> = self
.state .state
.surrealdb_mem .surrealdb_mem
.select((ResourceEnum::UsersCache.to_string(), email)) .select((ResourceEnum::UsersCache.to_string(), email))
@@ -54,7 +54,7 @@ impl<'a> AuthRepository<'a> {
} }
pub async fn query_delete_stored_user(&self, email: String) -> Result<String> { pub async fn query_delete_stored_user(&self, email: String) -> Result<String> {
let record: Option<UsersItemDtoRaw> = self let record: Option<UsersDetailQueryDto> = self
.state .state
.surrealdb_mem .surrealdb_mem
.delete((ResourceEnum::UsersCache.to_string(), email)) .delete((ResourceEnum::UsersCache.to_string(), email))
+46 -50
View File
@@ -1,15 +1,15 @@
use super::{ use super::{
AuthLoginRequestDto, AuthLoginResponsetDto, AuthNewPasswordRequestDto, AuthLoginRequestDto, AuthLoginResponsetDto, AuthNewPasswordRequestDto,
AuthRefreshTokenRequestDto, AuthRegisterRequestDto, AuthRepository, AuthRefreshTokenRequestDto, AuthRegisterRequestDto, AuthRepository,
AuthResendOtpRequestDto, AuthUserItemDto, AuthVerifyEmailRequestDto, TokenDto, AuthResendOtpRequestDto, AuthVerifyEmailRequestDto, TokenDto,
}; };
use crate::{ use crate::{
common_response, decode_refresh_token, encode_access_token, encode_refresh_token, AppState, Env, ResourceEnum, ResponseSuccessDto, RolesEnum, RolesRepository,
UsersDetailItemDto, UsersRepository, UsersSchema, common_response,
decode_refresh_token, encode_access_token, encode_refresh_token,
encode_reset_password_token, extract_email_token, generate_otp, get_iso_date, encode_reset_password_token, extract_email_token, generate_otp, get_iso_date,
hash_password, make_thing, send_email, success_response, validate_request, hash_password, make_thing, send_email, success_response, validate_request,
verify_password, AppState, Env, ResourceEnum, ResponseSuccessDto, RolesEnum, verify_password,
RolesItemDto, RolesRepository, UsersActiveInactiveSchema, UsersRepository,
UsersSchema, UsersSetNewPasswordSchema,
}; };
use axum::{http::StatusCode, response::Response}; use axum::{http::StatusCode, response::Response};
use surrealdb::Uuid; use surrealdb::Uuid;
@@ -27,7 +27,6 @@ impl AuthService {
let user_repo = UsersRepository::new(state); let user_repo = UsersRepository::new(state);
let auth_repo = AuthRepository::new(state); let auth_repo = AuthRepository::new(state);
let role_repo = RolesRepository::new(state);
match user_repo.query_user_by_email(payload.email.clone()).await { match user_repo.query_user_by_email(payload.email.clone()).await {
Ok(user) => { Ok(user) => {
@@ -54,7 +53,7 @@ impl AuthService {
return common_response( return common_response(
StatusCode::INTERNAL_SERVER_ERROR, StatusCode::INTERNAL_SERVER_ERROR,
"Failed to generate access token", "Failed to generate access token",
) );
} }
}; };
@@ -64,34 +63,13 @@ impl AuthService {
return common_response( return common_response(
StatusCode::INTERNAL_SERVER_ERROR, StatusCode::INTERNAL_SERVER_ERROR,
"Failed to generate refresh token", "Failed to generate refresh token",
) );
} }
}; };
let role_response = role_repo
.query_role_by_id(user.role.id.id.to_raw())
.await
.unwrap();
let response = ResponseSuccessDto { let response = ResponseSuccessDto {
data: AuthLoginResponsetDto { data: AuthLoginResponsetDto {
user: AuthUserItemDto { user: UsersDetailItemDto::from(&user),
id: user.id.id.to_raw(),
fullname: user.fullname.clone(),
email: user.email.clone(),
is_active: user.is_active.clone(),
avatar: user.avatar.clone(),
phone_number: user.phone_number.clone(),
gender: user.gender.clone(),
birthdate: user.birthdate.clone(),
role: RolesItemDto {
id: role_response.id,
name: role_response.name,
permissions: role_response.permissions,
created_at: role_response.created_at,
updated_at: role_response.updated_at,
},
},
token: TokenDto { token: TokenDto {
access_token, access_token,
refresh_token, refresh_token,
@@ -116,6 +94,7 @@ impl AuthService {
if let Err((status, message)) = validate_request(&payload) { if let Err((status, message)) = validate_request(&payload) {
return common_response(status, &message); return common_response(status, &message);
} }
let user_repo = UsersRepository::new(state); let user_repo = UsersRepository::new(state);
let auth_repo = AuthRepository::new(state); let auth_repo = AuthRepository::new(state);
let role_repo = RolesRepository::new(state); let role_repo = RolesRepository::new(state);
@@ -126,6 +105,7 @@ impl AuthService {
Ok(role) => role, Ok(role) => role,
Err(_) => return common_response(StatusCode::BAD_REQUEST, "Role Not Found"), Err(_) => return common_response(StatusCode::BAD_REQUEST, "Role Not Found"),
}; };
if user_repo if user_repo
.query_user_by_email(payload.email.clone()) .query_user_by_email(payload.email.clone())
.await .await
@@ -133,6 +113,7 @@ impl AuthService {
{ {
return common_response(StatusCode::BAD_REQUEST, "User already exists"); return common_response(StatusCode::BAD_REQUEST, "User already exists");
} }
let hashed_password = match hash_password(&payload.password) { let hashed_password = match hash_password(&payload.password) {
Ok(hash) => hash, Ok(hash) => hash,
Err(_) => { Err(_) => {
@@ -142,16 +123,16 @@ impl AuthService {
); );
} }
}; };
let new_user = AuthRegisterRequestDto { let new_user = AuthRegisterRequestDto {
email: payload.email, email: payload.email,
password: hashed_password, password: hashed_password,
fullname: payload.fullname, fullname: payload.fullname,
student_type: payload.student_type,
phone_number: payload.phone_number, phone_number: payload.phone_number,
referral_code: payload.referral_code,
referred_by: payload.referred_by,
}; };
let otp = generate_otp::OtpManager::generate_otp(); let otp = generate_otp::OtpManager::generate_otp();
match auth_repo match auth_repo
.query_store_otp(new_user.email.clone(), otp.clone()) .query_store_otp(new_user.email.clone(), otp.clone())
.await .await
@@ -169,11 +150,13 @@ impl AuthService {
return common_response(StatusCode::INTERNAL_SERVER_ERROR, &err.to_string()); return common_response(StatusCode::INTERNAL_SERVER_ERROR, &err.to_string());
} }
} }
let role_thing = make_thing(&ResourceEnum::Roles.to_string(), &role.id); let role_thing = make_thing(&ResourceEnum::Roles.to_string(), &role.id);
let user_thing = make_thing( let user_thing = make_thing(
&ResourceEnum::Users.to_string(), &ResourceEnum::Users.to_string(),
&Uuid::new_v4().to_string(), &Uuid::new_v4().to_string(),
); );
match user_repo match user_repo
.query_create_user(UsersSchema { .query_create_user(UsersSchema {
id: user_thing, id: user_thing,
@@ -204,7 +187,11 @@ impl AuthService {
} }
let user_repo = UsersRepository::new(state); let user_repo = UsersRepository::new(state);
if user_repo.query_user_by_email(payload.email.clone()).await.is_err() { if user_repo
.query_user_by_email(payload.email.clone())
.await
.is_err()
{
return common_response(StatusCode::BAD_REQUEST, "User not found"); return common_response(StatusCode::BAD_REQUEST, "User not found");
} }
@@ -273,10 +260,10 @@ impl AuthService {
let user = match user_result { let user = match user_result {
Ok(user) => user, Ok(user) => user,
Err(err) if err.to_string().contains("User not found") => { Err(err) if err.to_string().contains("User not found") => {
return common_response(StatusCode::BAD_REQUEST, "User not found") return common_response(StatusCode::BAD_REQUEST, "User not found");
} }
Err(err) => { Err(err) => {
return common_response(StatusCode::INTERNAL_SERVER_ERROR, &err.to_string()) return common_response(StatusCode::INTERNAL_SERVER_ERROR, &err.to_string());
} }
}; };
let token = match encode_reset_password_token(user.email) { let token = match encode_reset_password_token(user.email) {
@@ -285,7 +272,7 @@ impl AuthService {
return common_response( return common_response(
StatusCode::INTERNAL_SERVER_ERROR, StatusCode::INTERNAL_SERVER_ERROR,
"Failed to generate access token", "Failed to generate access token",
) );
} }
}; };
let env = Env::new(); let env = Env::new();
@@ -310,15 +297,18 @@ impl AuthService {
let user_repo = UsersRepository::new(state); let user_repo = UsersRepository::new(state);
let auth_repo = AuthRepository::new(state); let auth_repo = AuthRepository::new(state);
let email = payload.email.clone(); let email = payload.email.clone();
let user = match user_repo.query_user_by_email(email.clone()).await {
Ok(user) if !user.is_deleted => user,
_ => return common_response(StatusCode::NOT_FOUND, "User not found"),
};
let patch = UsersSchema {
id: user.id.clone(),
is_active: true,
..UsersSchema::from(user)
};
match auth_repo.query_get_stored_otp(email.clone()).await { match auth_repo.query_get_stored_otp(email.clone()).await {
Ok(stored_otp) => match stored_otp == payload.otp { Ok(stored_otp) => match stored_otp == payload.otp {
true => match user_repo true => match user_repo.query_update_user(patch).await {
.query_active_inactive_user(
email.clone(),
UsersActiveInactiveSchema { is_active: true },
)
.await
{
Ok(_) => match auth_repo.query_delete_stored_otp(email).await { Ok(_) => match auth_repo.query_delete_stored_otp(email).await {
Ok(_) => common_response(StatusCode::OK, "Email verified successfully"), Ok(_) => common_response(StatusCode::OK, "Email verified successfully"),
Err(e) => { Err(e) => {
@@ -346,7 +336,7 @@ impl AuthService {
if let Err((status, message)) = validate_request(&payload) { if let Err((status, message)) = validate_request(&payload) {
return common_response(status, &message); return common_response(status, &message);
} }
let user_repo = UsersRepository::new(state); let repo = UsersRepository::new(state);
let email = match extract_email_token(payload.token.clone()) { let email = match extract_email_token(payload.token.clone()) {
Some(email) => email, Some(email) => email,
None => { None => {
@@ -362,12 +352,18 @@ impl AuthService {
); );
} }
}; };
match user_repo let user = match repo.query_user_by_email(email.clone()).await {
.query_update_password_user(email, UsersSetNewPasswordSchema { password }) Ok(user) if !user.is_deleted => user,
.await _ => return common_response(StatusCode::NOT_FOUND, "User not found"),
{ };
let patch = UsersSchema {
id: user.id.clone(),
password,
..Default::default()
};
match repo.query_update_user(patch).await {
Ok(msg) => common_response(StatusCode::OK, &msg), Ok(msg) => common_response(StatusCode::OK, &msg),
Err(err) => common_response(StatusCode::BAD_REQUEST, &err.to_string()), Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()),
} }
} }
} }
@@ -1,9 +1,9 @@
use super::{PermissionsItemDto, PermissionsItemDtoRaw, PermissionsSchema}; use super::{PermissionsItemDto, PermissionsSchema};
use crate::{ use crate::{
get_id, make_thing, query_list_with_meta, AppState, MetaRequestDto, ResourceEnum, AppState, MetaRequestDto, ResourceEnum, ResponseListSuccessDto, get_id,
ResponseListSuccessDto, make_thing, query_list_with_meta,
}; };
use anyhow::{bail, Result}; use anyhow::{Result, bail};
use imphnen_utils::extract_id; use imphnen_utils::extract_id;
pub struct PermissionsRepository<'a> { pub struct PermissionsRepository<'a> {
@@ -27,12 +27,15 @@ impl<'a> PermissionsRepository<'a> {
let filter_by = meta.filter_by.as_ref().unwrap(); let filter_by = meta.filter_by.as_ref().unwrap();
conditions.push(format!("{} = $filter", filter_by)); conditions.push(format!("{} = $filter", filter_by));
} }
let raw_result: ResponseListSuccessDto<Vec<PermissionsItemDtoRaw>> = query_list_with_meta( let raw_result: ResponseListSuccessDto<Vec<PermissionsSchema>> =
query_list_with_meta(
&self.state.surrealdb_ws, &self.state.surrealdb_ws,
&ResourceEnum::Permissions.to_string(), &ResourceEnum::Permissions.to_string(),
&meta, &meta,
conditions, conditions,
None, None,
"name",
None,
) )
.await?; .await?;
let transformed_data = raw_result let transformed_data = raw_result
@@ -69,9 +72,7 @@ impl<'a> PermissionsRepository<'a> {
&self, &self,
id: String, id: String,
) -> Result<PermissionsItemDto> { ) -> Result<PermissionsItemDto> {
let raw_result = self let raw_result = self.query_permission_by_id(id.clone()).await?;
.query_permission_by_id(id.clone())
.await?;
let transformed_data = PermissionsItemDto { let transformed_data = PermissionsItemDto {
id: extract_id(&raw_result.id), id: extract_id(&raw_result.id),
name: raw_result.name, name: raw_result.name,
@@ -81,7 +82,6 @@ impl<'a> PermissionsRepository<'a> {
Ok(transformed_data) Ok(transformed_data)
} }
pub async fn query_permission_by_name( pub async fn query_permission_by_name(
&self, &self,
name: String, name: String,
@@ -1,6 +1,6 @@
use crate::{make_thing, ResourceEnum}; use crate::{ResourceEnum, make_thing};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use surrealdb::{sql::Thing, Uuid}; use surrealdb::{Uuid, sql::Thing};
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PermissionsSchema { pub struct PermissionsSchema {
+11 -11
View File
@@ -95,19 +95,19 @@ pub struct UsersDetailItemDto {
} }
impl UsersDetailItemDto { impl UsersDetailItemDto {
pub fn from(dto: UsersDetailQueryDto) -> Self { pub fn from(dto: &UsersDetailQueryDto) -> Self {
Self { Self {
id: dto.id.id.to_raw(), id: dto.id.id.to_raw().clone(),
role: RolesDetailItemDto::from(&dto.role), role: RolesDetailItemDto::from(&dto.role),
fullname: dto.fullname, fullname: dto.fullname.clone(),
email: dto.email, email: dto.email.clone(),
avatar: dto.avatar, avatar: dto.avatar.clone(),
phone_number: dto.phone_number, phone_number: dto.phone_number.clone(),
is_active: dto.is_active, is_active: dto.is_active.clone(),
gender: dto.gender, gender: dto.gender.clone(),
birthdate: dto.birthdate, birthdate: dto.birthdate.clone(),
created_at: dto.created_at, created_at: dto.created_at.clone(),
updated_at: dto.updated_at, updated_at: dto.updated_at.clone(),
} }
} }
} }
+2 -2
View File
@@ -34,7 +34,7 @@ impl UsersService {
let repo = UsersRepository::new(state); let repo = UsersRepository::new(state);
match repo.query_user_by_id(id).await { match repo.query_user_by_id(id).await {
Ok(user) if !user.is_deleted => success_response(ResponseSuccessDto { Ok(user) if !user.is_deleted => success_response(ResponseSuccessDto {
data: UsersDetailItemDto::from(user), data: UsersDetailItemDto::from(&user),
}), }),
Ok(_) => common_response(StatusCode::NOT_FOUND, "User not found"), Ok(_) => common_response(StatusCode::NOT_FOUND, "User not found"),
Err(e) => common_response(StatusCode::NOT_FOUND, &e.to_string()), Err(e) => common_response(StatusCode::NOT_FOUND, &e.to_string()),
@@ -49,7 +49,7 @@ impl UsersService {
}; };
match repo.query_user_by_email(email).await { match repo.query_user_by_email(email).await {
Ok(user) if !user.is_deleted => success_response(ResponseSuccessDto { Ok(user) if !user.is_deleted => success_response(ResponseSuccessDto {
data: UsersDetailItemDto::from(user), data: UsersDetailItemDto::from(&user),
}), }),
Ok(_) => common_response(StatusCode::NOT_FOUND, "User not found"), Ok(_) => common_response(StatusCode::NOT_FOUND, "User not found"),
Err(e) => common_response(StatusCode::NOT_FOUND, &e.to_string()), Err(e) => common_response(StatusCode::NOT_FOUND, &e.to_string()),
@@ -2,7 +2,7 @@ use axum::{
Extension, extract::Request, http::StatusCode, middleware::Next, Extension, extract::Request, http::StatusCode, middleware::Next,
response::Response, response::Response,
}; };
use imphnen_iam::{UsersItemDtoRaw, UsersRepository}; use imphnen_iam::{UsersDetailQueryDto, UsersRepository};
use imphnen_libs::AppState; use imphnen_libs::AppState;
use imphnen_utils::{common_response, extract_email}; use imphnen_utils::{common_response, extract_email};
use std::convert::Infallible; use std::convert::Infallible;
@@ -23,7 +23,7 @@ pub async fn auth_middleware(
} }
}; };
let repository = UsersRepository::new(&state); let repository = UsersRepository::new(&state);
let user: Option<UsersItemDtoRaw> = let user: Option<UsersDetailQueryDto> =
match repository.query_user_by_email(email).await { match repository.query_user_by_email(email).await {
Ok(user) => Some(user), Ok(user) => Some(user),
Err(err) => { Err(err) => {
+6 -5
View File
@@ -1,10 +1,11 @@
#[cfg(test)] #[cfg(test)]
mod auth_repository_test { mod auth_repository_test {
use crate::{ use crate::{
create_mock_app_state, get_iso_date, make_thing, AuthOtpSchema, AuthRepository, AuthOtpSchema, AuthRepository, ResourceEnum, UsersRepository, UsersSchema,
ResourceEnum, RolesItemDtoRaw, UsersItemDtoRaw, UsersRepository, UsersSchema, create_mock_app_state, get_iso_date, make_thing,
}; };
use chrono::{Duration, Utc}; use chrono::{Duration, Utc};
use imphnen_iam::{RolesDetailQueryDto, UsersDetailQueryDto};
use surrealdb::Uuid; use surrealdb::Uuid;
fn create_mock_user(email: &str) -> UsersSchema { fn create_mock_user(email: &str) -> UsersSchema {
@@ -47,7 +48,7 @@ mod auth_repository_test {
let state = create_mock_app_state().await; let state = create_mock_app_state().await;
let auth_repo = AuthRepository::new(&state); let auth_repo = AuthRepository::new(&state);
let email = "delete_me@example.com".to_string(); let email = "delete_me@example.com".to_string();
let mock_user = UsersItemDtoRaw { let mock_user = UsersDetailQueryDto {
id: make_thing(&ResourceEnum::UsersCache.to_string(), &email), id: make_thing(&ResourceEnum::UsersCache.to_string(), &email),
fullname: "Test User".into(), fullname: "Test User".into(),
email: email.clone(), email: email.clone(),
@@ -56,7 +57,7 @@ mod auth_repository_test {
is_active: true, is_active: true,
gender: None, gender: None,
birthdate: None, birthdate: None,
role: RolesItemDtoRaw { role: RolesDetailQueryDto {
id: make_thing("app_roles", &Uuid::new_v4().to_string()), id: make_thing("app_roles", &Uuid::new_v4().to_string()),
name: "Dummy Role".into(), name: "Dummy Role".into(),
permissions: vec![], permissions: vec![],
@@ -69,7 +70,7 @@ mod auth_repository_test {
created_at: get_iso_date(), created_at: get_iso_date(),
updated_at: get_iso_date(), updated_at: get_iso_date(),
}; };
let _: Option<UsersItemDtoRaw> = state let _: Option<UsersDetailQueryDto> = state
.surrealdb_mem .surrealdb_mem
.create((ResourceEnum::UsersCache.to_string(), email.clone())) .create((ResourceEnum::UsersCache.to_string(), email.clone()))
.content(mock_user) .content(mock_user)
+5 -3
View File
@@ -1,11 +1,11 @@
use crate::AppState; use crate::AppState;
use crate::{ use crate::{
create_mock_app_state, roles_router, v1::roles_repository::RolesRepository, RolesRequestCreateDto, RolesRequestUpdateDto, create_mock_app_state, roles_router,
RolesRequestCreateDto, RolesRequestUpdateDto, v1::roles_repository::RolesRepository,
}; };
use axum::http::StatusCode;
use axum::Extension; use axum::Extension;
use axum::Router; use axum::Router;
use axum::http::StatusCode;
use axum_test::TestServer; use axum_test::TestServer;
pub fn create_test_app(state: AppState) -> TestServer { pub fn create_test_app(state: AppState) -> TestServer {
@@ -74,6 +74,7 @@ async fn test_put_update_role_should_return_200() {
let payload = RolesRequestUpdateDto { let payload = RolesRequestUpdateDto {
name: Some("Updated Role".into()), name: Some("Updated Role".into()),
permissions: Some(vec![]), permissions: Some(vec![]),
overwrite: None,
}; };
let res = server let res = server
.put(&format!("/v1/roles/update/{}", id)) .put(&format!("/v1/roles/update/{}", id))
@@ -131,6 +132,7 @@ async fn test_put_update_nonexistent_role_should_return_404() {
let payload = RolesRequestUpdateDto { let payload = RolesRequestUpdateDto {
name: Some("Does Not Exist".into()), name: Some("Does Not Exist".into()),
permissions: Some(vec![]), permissions: Some(vec![]),
overwrite: None,
}; };
let res = server let res = server
.put("/v1/roles/update/nonexistent-id") .put("/v1/roles/update/nonexistent-id")
@@ -100,6 +100,7 @@ async fn test_query_update_role_should_update_name_and_permissions() {
let update_payload = RolesRequestUpdateDto { let update_payload = RolesRequestUpdateDto {
name: Some("Updated Role Name".into()), name: Some("Updated Role Name".into()),
permissions: Some(vec![new_perm_id.clone()]), permissions: Some(vec![new_perm_id.clone()]),
overwrite: None,
}; };
let update_result = repo let update_result = repo
.query_update_role(existing_role_id.clone(), update_payload) .query_update_role(existing_role_id.clone(), update_payload)
@@ -158,6 +159,7 @@ async fn test_query_update_role_should_fallback_to_existing_permissions_if_none_
let update_payload = RolesRequestUpdateDto { let update_payload = RolesRequestUpdateDto {
name: Some("Updated Role Name".into()), name: Some("Updated Role Name".into()),
permissions: None, permissions: None,
overwrite: None,
}; };
let update_res = repo let update_res = repo
.query_update_role(existing_id.clone(), update_payload) .query_update_role(existing_id.clone(), update_payload)
+2 -85
View File
@@ -1,8 +1,5 @@
use crate::{create_mock_app_state, create_test_user, RolesRepository}; use crate::{MetaRequestDto, UsersRepository};
use crate::{ use crate::{RolesRepository, create_mock_app_state, create_test_user};
MetaRequestDto, UsersActiveInactiveSchema, UsersRepository,
UsersSetNewPasswordSchema,
};
async fn get_role_id(state: &crate::AppState) -> String { async fn get_role_id(state: &crate::AppState) -> String {
RolesRepository::new(state) RolesRepository::new(state)
@@ -31,35 +28,6 @@ async fn test_create_and_get_user() {
assert_eq!(fetched.unwrap().email, "testuser@example.com"); assert_eq!(fetched.unwrap().email, "testuser@example.com");
} }
#[tokio::test]
async fn test_update_password_user() {
let app_state = create_mock_app_state().await;
let repo = UsersRepository::new(&app_state);
let role_repo = RolesRepository::new(&app_state);
let role_id = role_repo
.query_role_by_name("Student".into())
.await
.expect("Role not found")
.id;
let email = "changepass@example.com";
let user = create_test_user(email, "Change Password", true, &role_id);
repo.query_create_user(user).await.unwrap();
let result = repo
.query_update_password_user(
email.to_string(),
UsersSetNewPasswordSchema {
password: "newpass".into(),
},
)
.await;
assert!(
result.is_ok(),
"Update password failed with error: {:?}",
result.err()
);
dbg!(result.unwrap());
}
#[tokio::test] #[tokio::test]
async fn test_query_user_list_with_pagination_and_filter() { async fn test_query_user_list_with_pagination_and_filter() {
let app_state = create_mock_app_state().await; let app_state = create_mock_app_state().await;
@@ -116,57 +84,6 @@ async fn test_query_user_list_basic() {
assert_eq!(result.meta.as_ref().unwrap().per_page.unwrap(), 5); assert_eq!(result.meta.as_ref().unwrap().per_page.unwrap(), 5);
} }
#[tokio::test]
async fn test_query_active_inactive_user() {
let app_state = create_mock_app_state().await;
let repo = UsersRepository::new(&app_state);
let email = "inactive@example.com";
let user = create_test_user(
email,
"Inactive User",
false,
&get_role_id(&app_state).await,
);
repo.query_create_user(user).await.unwrap();
let result = repo
.query_active_inactive_user(
email.into(),
UsersActiveInactiveSchema { is_active: true },
)
.await;
assert!(result.is_ok());
let updated = repo
.query_user_by_email("inactive@example.com".into())
.await
.unwrap();
assert_eq!(updated.is_active, true);
}
#[tokio::test]
async fn test_query_user_by_invalid_email_should_fail() {
let app_state = create_mock_app_state().await;
let repo = UsersRepository::new(&app_state);
let result = repo
.query_user_by_email("nonexistent@example.com".into())
.await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_query_update_password_for_nonexistent_user_should_fail() {
let app_state = create_mock_app_state().await;
let repo = UsersRepository::new(&app_state);
let result = repo
.query_update_password_user(
"ghost@example.com".into(),
UsersSetNewPasswordSchema {
password: "secret".into(),
},
)
.await;
assert!(result.is_err());
}
#[tokio::test] #[tokio::test]
async fn test_query_delete_user() { async fn test_query_delete_user() {
let app_state = create_mock_app_state().await; let app_state = create_mock_app_state().await;
-1
View File
@@ -1,6 +1,5 @@
pub use imphnen_entities::*; pub use imphnen_entities::*;
pub use imphnen_iam::*; pub use imphnen_iam::*;
pub use imphnen_utils::*;
#[cfg(test)] #[cfg(test)]
pub mod iam; pub mod iam;