chore: normalize code
This commit is contained in:
Generated
+11
-35
@@ -1847,7 +1847,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed"
|
||||
|
||||
[[package]]
|
||||
name = "imphnen-backend-service"
|
||||
name = "imphnen-backend"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
@@ -1855,7 +1855,7 @@ dependencies = [
|
||||
"axum-test",
|
||||
"chrono",
|
||||
"imphnen-entities",
|
||||
"imphnen-gateway-service",
|
||||
"imphnen-gateway",
|
||||
"imphnen-libs",
|
||||
"imphnen-utils",
|
||||
"lazy_static",
|
||||
@@ -1872,31 +1872,7 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imphnen-cms-service"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
"axum-test",
|
||||
"chrono",
|
||||
"imphnen-entities",
|
||||
"imphnen-libs",
|
||||
"imphnen-utils",
|
||||
"lazy_static",
|
||||
"rand 0.9.0",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"surrealdb",
|
||||
"tokio",
|
||||
"tower-http",
|
||||
"utoipa",
|
||||
"utoipa-swagger-ui",
|
||||
"validator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imphnen-dimentorin-service"
|
||||
name = "imphnen-dimentorin"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
@@ -1931,7 +1907,7 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imphnen-gacha-service"
|
||||
name = "imphnen-gacha"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
@@ -1955,7 +1931,7 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imphnen-gateway-service"
|
||||
name = "imphnen-gateway"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
@@ -1963,9 +1939,9 @@ dependencies = [
|
||||
"axum-test",
|
||||
"chrono",
|
||||
"imphnen-entities",
|
||||
"imphnen-iam-service",
|
||||
"imphnen-iam",
|
||||
"imphnen-libs",
|
||||
"imphnen-middleware-service",
|
||||
"imphnen-middleware",
|
||||
"imphnen-utils",
|
||||
"lazy_static",
|
||||
"rand 0.9.0",
|
||||
@@ -1981,7 +1957,7 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imphnen-iam-service"
|
||||
name = "imphnen-iam"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
@@ -2020,7 +1996,7 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imphnen-middleware-service"
|
||||
name = "imphnen-middleware"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
@@ -2029,7 +2005,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"futures",
|
||||
"imphnen-entities",
|
||||
"imphnen-iam-service",
|
||||
"imphnen-iam",
|
||||
"imphnen-libs",
|
||||
"imphnen-utils",
|
||||
"lazy_static",
|
||||
@@ -4317,7 +4293,7 @@ dependencies = [
|
||||
"axum-test",
|
||||
"chrono",
|
||||
"imphnen-entities",
|
||||
"imphnen-iam-service",
|
||||
"imphnen-iam",
|
||||
"imphnen-libs",
|
||||
"imphnen-utils",
|
||||
"serde_json",
|
||||
|
||||
+11
-11
@@ -1,18 +1,16 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
# "imphnen-*",
|
||||
"imphnen-utils",
|
||||
"imphnen-libs",
|
||||
"imphnen-entities",
|
||||
"imphnen-cms-service",
|
||||
"imphnen-iam-service",
|
||||
"imphnen-gacha-service",
|
||||
"imphnen-gateway-service",
|
||||
"imphnen-dimentorin-service",
|
||||
"imphnen-middleware-service",
|
||||
"imphnen-core-service",
|
||||
"tests",
|
||||
"imphnen-iam",
|
||||
"imphnen-libs",
|
||||
"imphnen-utils",
|
||||
"imphnen-gacha",
|
||||
"imphnen-gateway",
|
||||
"imphnen-backend",
|
||||
"imphnen-entities",
|
||||
"imphnen-dimentorin",
|
||||
"imphnen-middleware",
|
||||
]
|
||||
|
||||
|
||||
@@ -38,6 +36,8 @@ lazy_static = "1.4.0"
|
||||
regex = "1.11.1"
|
||||
axum-test = "17.2.0"
|
||||
fancy-regex = "0.14.0"
|
||||
futures = "0.3.31"
|
||||
tower = "0.5.2"
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
[package]
|
||||
name = "imphnen-backend-service"
|
||||
name = "imphnen-backend"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" }
|
||||
imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" }
|
||||
imphnen-gateway = { version = "0.1.0", path = "../imphnen-gateway" }
|
||||
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
|
||||
imphnen-gateway-service = { version = "0.1.0", path = "../imphnen-gateway-service" }
|
||||
axum.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
@@ -1,4 +1,4 @@
|
||||
use imphnen_gateway_service::gateway_service;
|
||||
use imphnen_gateway::gateway_service;
|
||||
use imphnen_libs::axum_init;
|
||||
|
||||
#[tokio::main]
|
||||
+1
-1
@@ -140,6 +140,6 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
.await?;
|
||||
println!("✅ Inserted: {}", name);
|
||||
}
|
||||
println!("✅ Semua permissions berhasil disimpan ke SurrealDB!");
|
||||
println!("✅ All Permissions seeded");
|
||||
Ok(())
|
||||
}
|
||||
@@ -65,6 +65,6 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
.await?;
|
||||
println!("✅ Inserted role: {}", name);
|
||||
}
|
||||
println!("✅ Semua role berhasil disimpan ke SurrealDB!");
|
||||
println!("✅ All Roles seeded");
|
||||
Ok(())
|
||||
}
|
||||
+14
-14
@@ -19,24 +19,24 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
let role_permissions = vec![
|
||||
(
|
||||
"50133429-f4b1-4249-9f97-7b86e6ee9d86", // Staf
|
||||
"50133429-f4b1-4249-9f97-7b86e6ee9d86",
|
||||
vec![
|
||||
"7c15e31d-36e2-49f9-97db-138c03fb0cf6", // Read List Users
|
||||
"319ee593-ff0a-4f29-bbaf-9feb3174a3a6", // Read Detail Users
|
||||
"7c15e31d-36e2-49f9-97db-138c03fb0cf6",
|
||||
"319ee593-ff0a-4f29-bbaf-9feb3174a3a6",
|
||||
],
|
||||
),
|
||||
(
|
||||
"f6b03f25-e416-4893-ac88-caaa690afb07", // Admin
|
||||
"f6b03f25-e416-4893-ac88-caaa690afb07",
|
||||
vec![
|
||||
"023e2dfe-93c3-4008-94a8-b5dff403f73b", // Create Users
|
||||
"96df0689-2ae9-4894-bf00-837c19415e5c", // Delete Users
|
||||
"98b3dc4c-0124-461f-afcd-166637c5e6e8", // Update Users
|
||||
"319ee593-ff0a-4f29-bbaf-9feb3174a3a6", // Read Detail Users
|
||||
"7c15e31d-36e2-49f9-97db-138c03fb0cf6", // Read List Users
|
||||
"9164ca6e-c7e3-4238-a15f-f36ab9577e7e", // Read List Roles
|
||||
"319ee593-ff0a-4f29-bbaf-9feb3174a3a2", // Create Roles
|
||||
"a00d5608-4c48-4542-845c-dfe004687022", // Update Roles
|
||||
"35b0d992-65c8-4b62-b030-e6e0320e4048", // Delete Roles
|
||||
"023e2dfe-93c3-4008-94a8-b5dff403f73b",
|
||||
"96df0689-2ae9-4894-bf00-837c19415e5c",
|
||||
"98b3dc4c-0124-461f-afcd-166637c5e6e8",
|
||||
"319ee593-ff0a-4f29-bbaf-9feb3174a3a6",
|
||||
"7c15e31d-36e2-49f9-97db-138c03fb0cf6",
|
||||
"9164ca6e-c7e3-4238-a15f-f36ab9577e7e",
|
||||
"319ee593-ff0a-4f29-bbaf-9feb3174a3a2",
|
||||
"a00d5608-4c48-4542-845c-dfe004687022",
|
||||
"35b0d992-65c8-4b62-b030-e6e0320e4048",
|
||||
],
|
||||
),
|
||||
];
|
||||
@@ -53,7 +53,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
.await?;
|
||||
println!("✅ Updated permissions for role ID: {}", role_id);
|
||||
}
|
||||
println!("✅ Semua roles telah diperbarui dengan permissions di SurrealDB!");
|
||||
println!("✅ All Roles permissions seeded");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -90,6 +90,6 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
println!("✅ Inserted user: {} ({})", fullname, email);
|
||||
}
|
||||
|
||||
println!("✅ Semua users berhasil disimpan ke SurrealDB!");
|
||||
println!("✅ All Users seeded");
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use imphnen_gateway_service::gateway_service;
|
||||
use imphnen_gateway::gateway_service;
|
||||
use imphnen_libs::axum_init;
|
||||
|
||||
#[tokio::main]
|
||||
@@ -1,14 +0,0 @@
|
||||
pub fn add(left: u64, right: u64) -> u64 {
|
||||
left + right
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
[package]
|
||||
name = "imphnen-cms-service"
|
||||
name = "imphnen-dimentorin"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
|
||||
imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" }
|
||||
imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" }
|
||||
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
|
||||
axum.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
@@ -6,6 +6,6 @@ edition = "2024"
|
||||
[dependencies]
|
||||
axum.workspace = true
|
||||
serde.workspace = true
|
||||
surrealdb = { workspace = true, features = ["kv-mem"] }
|
||||
thiserror.workspace = true
|
||||
utoipa.workspace = true
|
||||
surrealdb.workspace = true
|
||||
thiserror.workspace = true
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
[package]
|
||||
name = "imphnen-iam-service"
|
||||
name = "imphnen-gacha"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
|
||||
imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" }
|
||||
imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" }
|
||||
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
|
||||
axum.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
@@ -1,26 +0,0 @@
|
||||
[package]
|
||||
name = "imphnen-gateway-service"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
imphnen-iam-service = { version = "0.1.0", path = "../imphnen-iam-service" }
|
||||
imphnen-middleware-service = { version = "0.1.0", path = "../imphnen-middleware-service" }
|
||||
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
|
||||
imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" }
|
||||
imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" }
|
||||
axum.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
utoipa.workspace = true
|
||||
lazy_static.workspace = true
|
||||
regex.workspace = true
|
||||
validator.workspace = true
|
||||
axum-test.workspace = true
|
||||
surrealdb.workspace = true
|
||||
rand.workspace = true
|
||||
tokio.workspace = true
|
||||
chrono.workspace = true
|
||||
anyhow.workspace = true
|
||||
tower-http.workspace = true
|
||||
utoipa-swagger-ui.workspace = true
|
||||
@@ -1,12 +1,14 @@
|
||||
[package]
|
||||
name = "imphnen-dimentorin-service"
|
||||
name = "imphnen-gateway"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
|
||||
imphnen-iam = { version = "0.1.0", path = "../imphnen-iam" }
|
||||
imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" }
|
||||
imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" }
|
||||
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
|
||||
imphnen-middleware = { version = "0.1.0", path = "../imphnen-middleware" }
|
||||
axum.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
@@ -1,4 +1,4 @@
|
||||
use imphnen_iam_service::{
|
||||
use imphnen_iam::{
|
||||
auth, permissions, roles, users, AuthLoginRequestDto, AuthLoginResponsetDto, AuthResendOtpRequestDto, AuthVerifyEmailRequestDto,
|
||||
AuthNewPasswordRequestDto, AuthRefreshTokenRequestDto, MessageResponseDto, MetaRequestDto, MetaResponseDto, PermissionsItemDto, PermissionsRequestDto, ResponseListSuccessDto, ResponseSuccessDto, RolesItemDto, RolesRequestCreateDto, RolesRequestUpdateDto, TokenDto, UsersCreateRequestDto, UsersDetailItemDto, UsersItemDto, UsersListItemDto, UsersUpdateRequestDto
|
||||
};
|
||||
@@ -2,12 +2,12 @@ use axum::{
|
||||
Extension, Router, middleware::from_fn, response::Redirect, routing::get,
|
||||
};
|
||||
use imphnen_entities::{AppState, SurrealMemClient, SurrealWsClient};
|
||||
use imphnen_iam_service::{iam_protected_routes, iam_public_routes};
|
||||
use imphnen_iam::{iam_protected_routes, iam_public_routes};
|
||||
use imphnen_middleware::{auth_middleware, cors_middleware};
|
||||
use utoipa_swagger_ui::SwaggerUi;
|
||||
|
||||
pub mod docs;
|
||||
pub use docs::*;
|
||||
use imphnen_middleware_service::{auth_middleware, cors_middleware};
|
||||
use utoipa_swagger_ui::SwaggerUi;
|
||||
|
||||
pub async fn gateway_service(
|
||||
surrealdb_ws: SurrealWsClient,
|
||||
@@ -1,66 +0,0 @@
|
||||
use crate::{PermissionsItemDto, PermissionsItemDtoRaw};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use surrealdb::sql::Thing;
|
||||
use utoipa::ToSchema;
|
||||
use validator::Validate;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
|
||||
pub struct RolesRequestUpdateDto {
|
||||
#[validate(length(min = 1, message = "Role name must not be empty"))]
|
||||
pub name: Option<String>,
|
||||
pub permissions: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
|
||||
pub struct RolesRequestCreateDto {
|
||||
#[validate(length(min = 1, message = "Role name must not be empty"))]
|
||||
pub name: String,
|
||||
pub permissions: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct RolesItemListDto {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub created_at: Option<String>,
|
||||
pub updated_at: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct RolesItemDto {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub permissions: Vec<PermissionsItemDto>,
|
||||
pub created_at: Option<String>,
|
||||
pub updated_at: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct RolesItemByIdDto {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub is_deleted: bool,
|
||||
pub permissions: Vec<PermissionsItemDto>,
|
||||
pub created_at: Option<String>,
|
||||
pub updated_at: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct RolesItemByIdDtoRaw {
|
||||
pub id: Thing,
|
||||
pub name: String,
|
||||
pub permissions: Vec<PermissionsItemDtoRaw>,
|
||||
pub is_deleted: bool,
|
||||
pub created_at: Option<String>,
|
||||
pub updated_at: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct RolesItemDtoRaw {
|
||||
pub id: Thing,
|
||||
pub name: String,
|
||||
pub permissions: Vec<PermissionsItemDtoRaw>,
|
||||
pub is_deleted: bool,
|
||||
pub created_at: Option<String>,
|
||||
pub updated_at: Option<String>,
|
||||
}
|
||||
@@ -1,317 +0,0 @@
|
||||
use super::{
|
||||
UsersActiveInactiveSchema, UsersItemDto, UsersItemDtoRaw, UsersListItemDto,
|
||||
UsersListItemDtoRaw, UsersSchema, UsersSetNewPasswordSchema,
|
||||
};
|
||||
use crate::{
|
||||
extract_id, get_id, make_thing, query_list_with_meta, AppState, MetaRequestDto,
|
||||
PermissionsItemDto, PermissionsItemDtoRaw, ResourceEnum, ResponseListSuccessDto,
|
||||
RolesItemDto, RolesItemDtoRaw,
|
||||
};
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
|
||||
pub struct UsersRepository<'a> {
|
||||
state: &'a AppState,
|
||||
}
|
||||
|
||||
impl<'a> UsersRepository<'a> {
|
||||
pub fn new(state: &'a AppState) -> Self {
|
||||
Self { state }
|
||||
}
|
||||
|
||||
pub async fn query_user_list(
|
||||
&self,
|
||||
meta: MetaRequestDto,
|
||||
) -> Result<ResponseListSuccessDto<Vec<UsersListItemDto>>> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let mut conditions = vec!["is_deleted = false".to_string()];
|
||||
if let Some(search) = meta.search.as_deref() {
|
||||
if !search.is_empty() {
|
||||
conditions.push("string::contains(fullname ?? '', $search)".to_string());
|
||||
}
|
||||
}
|
||||
if let (Some(filter_by), Some(_filter)) =
|
||||
(meta.filter_by.as_ref(), meta.filter.as_ref())
|
||||
{
|
||||
conditions.push(format!("{} = $filter", filter_by));
|
||||
}
|
||||
let where_clause = if !conditions.is_empty() {
|
||||
format!("WHERE {}", conditions.join(" AND "))
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let limit = meta.per_page.unwrap_or(10);
|
||||
let page = meta.page.unwrap_or(1);
|
||||
if page < 1 || limit < 1 {
|
||||
return Err(anyhow!("Invalid pagination parameters"));
|
||||
}
|
||||
let start = (page - 1) * limit;
|
||||
let select_query = format!(
|
||||
"
|
||||
SELECT
|
||||
id,
|
||||
role.name AS role,
|
||||
fullname,
|
||||
email,
|
||||
avatar,
|
||||
phone_number,
|
||||
is_active,
|
||||
gender,
|
||||
birthdate,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM {}
|
||||
{}
|
||||
LIMIT {} START {}
|
||||
FETCH role
|
||||
",
|
||||
ResourceEnum::Users.to_string(),
|
||||
where_clause,
|
||||
limit,
|
||||
start
|
||||
);
|
||||
let raw_result = query_list_with_meta::<UsersListItemDtoRaw>(
|
||||
db,
|
||||
&ResourceEnum::Users.to_string(),
|
||||
&meta,
|
||||
vec![],
|
||||
Some(select_query),
|
||||
)
|
||||
.await?;
|
||||
let transformed_data = raw_result
|
||||
.data
|
||||
.into_iter()
|
||||
.map(|user| UsersListItemDto {
|
||||
id: extract_id(&user.id),
|
||||
fullname: user.fullname,
|
||||
email: user.email,
|
||||
avatar: user.avatar,
|
||||
phone_number: user.phone_number,
|
||||
is_active: user.is_active,
|
||||
role: user.role.unwrap_or_else(|| "-".into()),
|
||||
created_at: user.created_at,
|
||||
updated_at: user.updated_at,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
Ok(ResponseListSuccessDto {
|
||||
data: transformed_data,
|
||||
meta: raw_result.meta,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn query_user_by_email(&self, email: String) -> Result<UsersItemDtoRaw> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let sql = format!(
|
||||
"SELECT *, role AS role FROM {} WHERE email = $email AND is_deleted = false LIMIT 1 FETCH role, role.permissions",
|
||||
ResourceEnum::Users.to_string()
|
||||
);
|
||||
|
||||
let response: Option<UsersItemDtoRaw> = db
|
||||
.query(sql)
|
||||
.bind(("email", email.clone()))
|
||||
.await?
|
||||
.take(0)?;
|
||||
|
||||
match response {
|
||||
Some(user) if !user.role.is_deleted => {
|
||||
let permissions = user
|
||||
.role
|
||||
.permissions
|
||||
.into_iter()
|
||||
.map(|perm| PermissionsItemDtoRaw {
|
||||
id: perm.id,
|
||||
name: perm.name,
|
||||
created_at: perm.created_at,
|
||||
updated_at: perm.updated_at,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
Ok(UsersItemDtoRaw {
|
||||
id: user.id,
|
||||
fullname: user.fullname,
|
||||
email: user.email,
|
||||
avatar: user.avatar,
|
||||
phone_number: user.phone_number,
|
||||
is_active: user.is_active,
|
||||
is_deleted: user.is_deleted,
|
||||
gender: user.gender,
|
||||
birthdate: user.birthdate,
|
||||
password: user.password,
|
||||
created_at: user.created_at,
|
||||
updated_at: user.updated_at,
|
||||
role: RolesItemDtoRaw {
|
||||
id: user.role.id,
|
||||
name: user.role.name,
|
||||
permissions,
|
||||
created_at: user.role.created_at,
|
||||
updated_at: user.role.updated_at,
|
||||
is_deleted: user.role.is_deleted,
|
||||
},
|
||||
})
|
||||
}
|
||||
_ => bail!("User not found"),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn query_user_by_id(&self, id: String) -> Result<UsersItemDto> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
|
||||
let query = format!(
|
||||
"SELECT *, role AS role FROM app_users:⟨{}⟩ FETCH role, role.permissions",
|
||||
id
|
||||
);
|
||||
|
||||
let mut result = db.query(query).await?;
|
||||
|
||||
let response: Option<UsersItemDtoRaw> = result.take(0)?;
|
||||
|
||||
match response {
|
||||
Some(user) if !user.role.is_deleted => {
|
||||
let permissions = user
|
||||
.role
|
||||
.permissions
|
||||
.into_iter()
|
||||
.map(|perm| PermissionsItemDto {
|
||||
id: extract_id(&perm.id),
|
||||
name: perm.name,
|
||||
created_at: perm.created_at,
|
||||
updated_at: perm.updated_at,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(UsersItemDto {
|
||||
id: extract_id(&user.id),
|
||||
fullname: user.fullname,
|
||||
email: user.email,
|
||||
avatar: user.avatar,
|
||||
phone_number: user.phone_number,
|
||||
is_active: user.is_active,
|
||||
is_deleted: user.is_deleted,
|
||||
gender: user.gender,
|
||||
birthdate: user.birthdate,
|
||||
password: user.password,
|
||||
role: RolesItemDto {
|
||||
id: extract_id(&user.role.id),
|
||||
name: user.role.name,
|
||||
permissions,
|
||||
created_at: user.role.created_at,
|
||||
updated_at: user.role.updated_at,
|
||||
},
|
||||
created_at: user.created_at,
|
||||
updated_at: user.updated_at,
|
||||
})
|
||||
}
|
||||
_ => bail!("User not found"),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn query_create_user(&self, data: UsersSchema) -> Result<String> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let record: Option<UsersSchema> = db
|
||||
.create(ResourceEnum::Users.to_string())
|
||||
.content(data)
|
||||
.await?;
|
||||
match record {
|
||||
Some(_) => Ok("Success create user".into()),
|
||||
None => bail!("Failed to create user"),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn query_update_user(&self, data: UsersSchema) -> Result<String> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let record_key = get_id(&data.id)?;
|
||||
let existing = self.query_user_by_id(data.id.id.to_raw()).await?;
|
||||
if existing.is_deleted {
|
||||
bail!("User already deleted");
|
||||
}
|
||||
let merged = UsersSchema {
|
||||
password: existing.password,
|
||||
created_at: existing.created_at,
|
||||
role: make_thing("app_roles", &existing.role.id),
|
||||
..data.clone()
|
||||
};
|
||||
let record: Option<UsersSchema> = db.update(record_key).merge(merged).await?;
|
||||
match record {
|
||||
Some(_) => Ok("Success update user".into()),
|
||||
None => bail!("Failed to update user"),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn query_active_inactive_user(
|
||||
&self,
|
||||
email: String,
|
||||
data: UsersActiveInactiveSchema,
|
||||
) -> Result<String> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let user = self.query_user_by_email(email.clone()).await?;
|
||||
if user.is_deleted {
|
||||
bail!("User already deleted");
|
||||
}
|
||||
let record_key = get_id(&user.id)?;
|
||||
let record: Option<UsersSchema> = db
|
||||
.update(record_key)
|
||||
.merge(UsersActiveInactiveSchema {
|
||||
is_active: data.is_active,
|
||||
})
|
||||
.await?;
|
||||
match record {
|
||||
Some(_) => Ok("Success update user".into()),
|
||||
None => bail!("Failed to update user"),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn query_active_inactive_user_by_id(
|
||||
&self,
|
||||
id: String,
|
||||
data: UsersActiveInactiveSchema,
|
||||
) -> Result<String> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let record: Option<UsersSchema> = db
|
||||
.update((ResourceEnum::Users.to_string(), id))
|
||||
.merge(UsersActiveInactiveSchema {
|
||||
is_active: data.is_active,
|
||||
})
|
||||
.await?;
|
||||
match record {
|
||||
Some(_) => Ok("Success update user".into()),
|
||||
None => bail!("Failed to update user"),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn query_update_password_user(
|
||||
&self,
|
||||
email: String,
|
||||
data: UsersSetNewPasswordSchema,
|
||||
) -> Result<String> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let user = self.query_user_by_email(email).await?;
|
||||
let record: Option<UsersSetNewPasswordSchema> = db
|
||||
.update((ResourceEnum::Users.to_string(), user.id.id.to_raw()))
|
||||
.merge(UsersSetNewPasswordSchema {
|
||||
password: data.password.clone(),
|
||||
})
|
||||
.await?;
|
||||
dbg!(record.clone());
|
||||
match record {
|
||||
Some(_) => Ok("Success update password user".into()),
|
||||
None => bail!("Failed to update password user"),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn query_delete_user(&self, id: String) -> Result<String> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let user_id = make_thing(&ResourceEnum::Users.to_string(), &id);
|
||||
let user = self.query_user_by_id(user_id.id.to_raw()).await?;
|
||||
if user.is_deleted {
|
||||
bail!("User already deleted");
|
||||
}
|
||||
let id = make_thing(&ResourceEnum::Users.to_string(), &user.id);
|
||||
let record_key = get_id(&id)?;
|
||||
let record: Option<UsersSchema> = db
|
||||
.update(record_key)
|
||||
.merge(serde_json::json!({ "is_deleted": true }))
|
||||
.await?;
|
||||
match record {
|
||||
Some(_) => Ok("Success delete user".into()),
|
||||
None => bail!("Failed to delete user"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
use crate::{get_iso_date, make_thing, ResourceEnum};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use surrealdb::{sql::Thing, Uuid};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct UsersSchema {
|
||||
pub id: Thing,
|
||||
pub fullname: String,
|
||||
pub email: String,
|
||||
pub password: String,
|
||||
pub avatar: Option<String>,
|
||||
pub phone_number: String,
|
||||
pub is_active: bool,
|
||||
pub is_deleted: bool,
|
||||
pub gender: Option<String>,
|
||||
pub birthdate: Option<String>,
|
||||
pub role: Thing,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
impl Default for UsersSchema {
|
||||
fn default() -> Self {
|
||||
UsersSchema {
|
||||
id: make_thing(
|
||||
&ResourceEnum::Users.to_string(),
|
||||
&Uuid::new_v4().to_string(),
|
||||
),
|
||||
fullname: String::new(),
|
||||
email: String::new(),
|
||||
password: String::new(),
|
||||
avatar: None,
|
||||
phone_number: String::new(),
|
||||
is_active: false,
|
||||
is_deleted: false,
|
||||
gender: None,
|
||||
birthdate: None,
|
||||
role: make_thing(
|
||||
&ResourceEnum::Roles.to_string(),
|
||||
&Uuid::new_v4().to_string(),
|
||||
),
|
||||
created_at: get_iso_date(),
|
||||
updated_at: get_iso_date(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct UsersSetNewPasswordSchema {
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct UsersActiveInactiveSchema {
|
||||
pub is_active: bool,
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
[package]
|
||||
name = "imphnen-gacha-service"
|
||||
name = "imphnen-iam"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
|
||||
imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" }
|
||||
imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" }
|
||||
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
|
||||
axum.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
@@ -7,6 +7,7 @@ pub mod v1;
|
||||
|
||||
pub use imphnen_entities::*;
|
||||
pub use imphnen_libs::*;
|
||||
pub use imphnen_utils::*;
|
||||
pub use v1::*;
|
||||
|
||||
pub fn create_test_user(
|
||||
@@ -27,6 +28,7 @@ pub fn create_test_user(
|
||||
gender: None,
|
||||
birthdate: None,
|
||||
role: make_thing("app_roles", role_id),
|
||||
..Default::default()
|
||||
created_at: get_iso_date(),
|
||||
updated_at: get_iso_date(),
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -18,7 +18,7 @@ pub struct PermissionsItemDto {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct PermissionsItemDtoRaw {
|
||||
pub struct PermissionsQueryDto {
|
||||
pub id: Thing,
|
||||
pub name: String,
|
||||
pub created_at: Option<String>,
|
||||
@@ -0,0 +1,40 @@
|
||||
use crate::{PermissionsItemDto, PermissionsQueryDto};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use surrealdb::sql::Thing;
|
||||
use utoipa::ToSchema;
|
||||
use validator::Validate;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
|
||||
pub struct RolesRequestDto {
|
||||
#[validate(length(min = 1, message = "Role name must not be empty"))]
|
||||
pub name: String,
|
||||
pub permissions: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct RolesListItemDto {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub permissions_count: u64,
|
||||
pub created_at: Option<String>,
|
||||
pub updated_at: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct RolesDetailItemDto {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub permissions: Vec<PermissionsItemDto>,
|
||||
pub created_at: Option<String>,
|
||||
pub updated_at: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct RolesDetailQueryDto {
|
||||
pub id: Thing,
|
||||
pub name: String,
|
||||
pub permissions: Vec<PermissionsQueryDto>,
|
||||
pub is_deleted: bool,
|
||||
pub created_at: Option<String>,
|
||||
pub updated_at: Option<String>,
|
||||
}
|
||||
+26
-28
@@ -1,3 +1,4 @@
|
||||
use crate::{RolesDetailQueryDto, RolesItemDto};
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -5,17 +6,10 @@ use surrealdb::sql::Thing;
|
||||
use utoipa::ToSchema;
|
||||
use validator::Validate;
|
||||
|
||||
use crate::{RolesItemDto, RolesItemDtoRaw};
|
||||
|
||||
lazy_static! {
|
||||
static ref PASSWORD_REGEX: Regex = Regex::new(r"^[A-Za-z\d@$!%*?&]{8,}$").unwrap();
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct UsersActiveInactiveRequestDto {
|
||||
pub is_active: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
|
||||
pub struct UsersCreateRequestDto {
|
||||
#[validate(
|
||||
@@ -23,6 +17,7 @@ pub struct UsersCreateRequestDto {
|
||||
email(message = "Email not valid")
|
||||
)]
|
||||
pub email: String,
|
||||
|
||||
#[validate(length(
|
||||
min = 8,
|
||||
message = "Password must have at least 8 characters"
|
||||
@@ -32,8 +27,10 @@ pub struct UsersCreateRequestDto {
|
||||
message = "Password must include uppercase, lowercase, number, and special character"
|
||||
))]
|
||||
pub password: String,
|
||||
|
||||
#[validate(length(min = 2, message = "Fullname at least have 2 character"))]
|
||||
pub fullname: String,
|
||||
|
||||
#[validate(length(
|
||||
min = 10,
|
||||
message = "Phone number at least have 10 character"
|
||||
@@ -71,23 +68,6 @@ pub struct UsersUpdateRequestDto {
|
||||
pub role_id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct UsersItemDto {
|
||||
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 is_deleted: bool,
|
||||
pub gender: Option<String>,
|
||||
pub birthdate: Option<String>,
|
||||
pub password: String,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct UsersDetailItemDto {
|
||||
pub id: String,
|
||||
@@ -99,6 +79,8 @@ pub struct UsersDetailItemDto {
|
||||
pub is_active: bool,
|
||||
pub gender: Option<String>,
|
||||
pub birthdate: Option<String>,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
@@ -115,9 +97,9 @@ pub struct UsersListItemDto {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct UsersListItemDtoRaw {
|
||||
pub struct UsersListQueryDto {
|
||||
pub id: Thing,
|
||||
pub role: Option<String>,
|
||||
pub role: RolesDetailQueryDto,
|
||||
pub fullname: String,
|
||||
pub email: String,
|
||||
pub avatar: Option<String>,
|
||||
@@ -127,8 +109,24 @@ pub struct UsersListItemDtoRaw {
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
impl UsersListQueryDto {
|
||||
pub fn list_from(&self, role: String) -> UsersListItemDto {
|
||||
UsersListItemDto {
|
||||
id: self.id.id.to_raw(),
|
||||
role,
|
||||
fullname: self.fullname.clone(),
|
||||
email: self.email.clone(),
|
||||
avatar: self.avatar.clone(),
|
||||
phone_number: self.phone_number.clone(),
|
||||
is_active: self.is_active,
|
||||
created_at: self.created_at.clone(),
|
||||
updated_at: self.updated_at.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct UsersItemDtoRaw {
|
||||
pub struct UsersDetailQueryDto {
|
||||
pub id: Thing,
|
||||
pub fullname: String,
|
||||
pub email: String,
|
||||
@@ -138,8 +136,8 @@ pub struct UsersItemDtoRaw {
|
||||
pub is_deleted: bool,
|
||||
pub gender: Option<String>,
|
||||
pub birthdate: Option<String>,
|
||||
pub role: RolesItemDtoRaw,
|
||||
pub password: String,
|
||||
pub role: RolesDetailQueryDto,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
use super::{UsersDetailQueryDto, UsersListItemDto, UsersListQueryDto, UsersSchema};
|
||||
use crate::{
|
||||
AppState, MetaRequestDto, PermissionsItemDto, PermissionsItemDtoRaw, ResourceEnum,
|
||||
ResponseListSuccessDto, RolesDetailQueryDto, extract_id, get_id, make_thing,
|
||||
query_list_with_meta,
|
||||
};
|
||||
use anyhow::{Result, bail};
|
||||
|
||||
pub struct UsersRepository<'a> {
|
||||
state: &'a AppState,
|
||||
}
|
||||
|
||||
pub fn build_user_by_field_query(field: &str) -> String {
|
||||
format!(
|
||||
r#"
|
||||
SELECT *, role AS role
|
||||
FROM {}
|
||||
WHERE {} = $value AND is_deleted = false
|
||||
LIMIT 1
|
||||
FETCH role, role.permissions
|
||||
"#,
|
||||
ResourceEnum::Users.to_string(),
|
||||
field
|
||||
)
|
||||
}
|
||||
|
||||
impl<'a> UsersRepository<'a> {
|
||||
pub fn new(state: &'a AppState) -> Self {
|
||||
Self { state }
|
||||
}
|
||||
|
||||
pub async fn query_user_list(
|
||||
&self,
|
||||
meta: MetaRequestDto,
|
||||
) -> Result<ResponseListSuccessDto<Vec<UsersListItemDto>>> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
|
||||
let raw_result = query_list_with_meta::<UsersListQueryDto>(
|
||||
db,
|
||||
&ResourceEnum::Users.to_string(),
|
||||
&meta,
|
||||
vec!["is_deleted = false".into()],
|
||||
None,
|
||||
"fullname",
|
||||
Some(vec![
|
||||
"email",
|
||||
"fullname",
|
||||
"id",
|
||||
"is_active",
|
||||
"is_deleted",
|
||||
"role",
|
||||
"role.permissions",
|
||||
]),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let data = raw_result
|
||||
.data
|
||||
.into_iter()
|
||||
.map(|schema| {
|
||||
let role = schema.clone().role.name;
|
||||
schema.list_from(role)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(ResponseListSuccessDto {
|
||||
data,
|
||||
meta: raw_result.meta,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn query_user_by_email(
|
||||
&self,
|
||||
email: String,
|
||||
) -> Result<UsersDetailQueryDto> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
|
||||
let sql = build_user_by_field_query("email");
|
||||
|
||||
let user_opt: Option<UsersDetailQueryDto> = db
|
||||
.query(sql)
|
||||
.bind(("email", email.clone()))
|
||||
.await?
|
||||
.take(0)?;
|
||||
|
||||
let Some(user) = user_opt else {
|
||||
bail!("User not found");
|
||||
};
|
||||
|
||||
if user.role.is_deleted {
|
||||
bail!("User not found");
|
||||
}
|
||||
|
||||
let permissions = user
|
||||
.role
|
||||
.permissions
|
||||
.into_iter()
|
||||
.map(|perm| PermissionsItemDtoRaw {
|
||||
id: perm.id,
|
||||
name: perm.name,
|
||||
created_at: perm.created_at,
|
||||
updated_at: perm.updated_at,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(UsersDetailQueryDto {
|
||||
id: user.id,
|
||||
fullname: user.fullname,
|
||||
email: user.email,
|
||||
avatar: user.avatar,
|
||||
phone_number: user.phone_number,
|
||||
is_active: user.is_active,
|
||||
is_deleted: user.is_deleted,
|
||||
gender: user.gender,
|
||||
birthdate: user.birthdate,
|
||||
password: user.password,
|
||||
created_at: user.created_at,
|
||||
updated_at: user.updated_at,
|
||||
role: RolesDetailQueryDto {
|
||||
id: user.role.id,
|
||||
name: user.role.name,
|
||||
created_at: user.role.created_at,
|
||||
updated_at: user.role.updated_at,
|
||||
is_deleted: user.role.is_deleted,
|
||||
permissions,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn query_user_by_id(&self, id: String) -> Result<UsersDetailQueryDto> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
|
||||
let sql = build_user_by_field_query(&make_thing("app_users", &id).to_raw());
|
||||
|
||||
let user_opt: Option<UsersDetailQueryDto> = db
|
||||
.query(sql)
|
||||
.bind(("email", make_thing("app_users", &id).to_raw()))
|
||||
.await?
|
||||
.take(0)?;
|
||||
|
||||
let Some(user) = user_opt else {
|
||||
bail!("User not found");
|
||||
};
|
||||
|
||||
if user.role.is_deleted {
|
||||
bail!("User not found");
|
||||
}
|
||||
|
||||
let permissions = user
|
||||
.role
|
||||
.permissions
|
||||
.into_iter()
|
||||
.map(|perm| PermissionsItemDtoRaw {
|
||||
id: perm.id,
|
||||
name: perm.name,
|
||||
created_at: perm.created_at,
|
||||
updated_at: perm.updated_at,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(UsersDetailQueryDto {
|
||||
id: user.id,
|
||||
fullname: user.fullname,
|
||||
email: user.email,
|
||||
avatar: user.avatar,
|
||||
phone_number: user.phone_number,
|
||||
is_active: user.is_active,
|
||||
is_deleted: user.is_deleted,
|
||||
gender: user.gender,
|
||||
birthdate: user.birthdate,
|
||||
password: user.password,
|
||||
created_at: user.created_at,
|
||||
updated_at: user.updated_at,
|
||||
role: RolesDetailQueryDto {
|
||||
id: user.role.id,
|
||||
name: user.role.name,
|
||||
created_at: user.role.created_at,
|
||||
updated_at: user.role.updated_at,
|
||||
is_deleted: user.role.is_deleted,
|
||||
permissions,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn query_create_user(&self, data: UsersSchema) -> Result<String> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let record: Option<UsersSchema> = db
|
||||
.create(ResourceEnum::Users.to_string())
|
||||
.content(data)
|
||||
.await?;
|
||||
match record {
|
||||
Some(_) => Ok("Success create user".into()),
|
||||
None => bail!("Failed to create user"),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn query_update_user(&self, data: UsersSchema) -> Result<String> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let record_key = get_id(&data.id)?;
|
||||
let existing = self.query_user_by_id(data.id.id.to_raw()).await?;
|
||||
if existing.is_deleted {
|
||||
bail!("User already deleted");
|
||||
}
|
||||
let merged = UsersSchema {
|
||||
password: existing.password,
|
||||
created_at: existing.created_at,
|
||||
role: make_thing("app_roles", &existing.role.id),
|
||||
..data.clone()
|
||||
};
|
||||
let record: Option<UsersSchema> = db.update(record_key).merge(merged).await?;
|
||||
match record {
|
||||
Some(_) => Ok("Success update user".into()),
|
||||
None => bail!("Failed to update user"),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn query_active_inactive_user(
|
||||
&self,
|
||||
email: String,
|
||||
data: UsersActiveInactiveSchema,
|
||||
) -> Result<String> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let user = self.query_user_by_email(email.clone()).await?;
|
||||
if user.is_deleted {
|
||||
bail!("User already deleted");
|
||||
}
|
||||
let record_key = get_id(&user.id)?;
|
||||
let record: Option<UsersSchema> = db
|
||||
.update(record_key)
|
||||
.merge(UsersActiveInactiveSchema {
|
||||
is_active: data.is_active,
|
||||
})
|
||||
.await?;
|
||||
match record {
|
||||
Some(_) => Ok("Success update user".into()),
|
||||
None => bail!("Failed to update user"),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn query_active_inactive_user_by_id(
|
||||
&self,
|
||||
id: String,
|
||||
data: UsersActiveInactiveSchema,
|
||||
) -> Result<String> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let record: Option<UsersSchema> = db
|
||||
.update((ResourceEnum::Users.to_string(), id))
|
||||
.merge(UsersActiveInactiveSchema {
|
||||
is_active: data.is_active,
|
||||
})
|
||||
.await?;
|
||||
match record {
|
||||
Some(_) => Ok("Success update user".into()),
|
||||
None => bail!("Failed to update user"),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn query_update_password_user(
|
||||
&self,
|
||||
email: String,
|
||||
data: UsersSetNewPasswordSchema,
|
||||
) -> Result<String> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let user = self.query_user_by_email(email).await?;
|
||||
let record: Option<UsersSetNewPasswordSchema> = db
|
||||
.update((ResourceEnum::Users.to_string(), user.id.id.to_raw()))
|
||||
.merge(UsersSetNewPasswordSchema {
|
||||
password: data.password.clone(),
|
||||
})
|
||||
.await?;
|
||||
dbg!(record.clone());
|
||||
match record {
|
||||
Some(_) => Ok("Success update password user".into()),
|
||||
None => bail!("Failed to update password user"),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn query_delete_user(&self, id: String) -> Result<String> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let user_id = make_thing(&ResourceEnum::Users.to_string(), &id);
|
||||
let user = self.query_user_by_id(user_id.id.to_raw()).await?;
|
||||
if user.is_deleted {
|
||||
bail!("User already deleted");
|
||||
}
|
||||
let id = make_thing(&ResourceEnum::Users.to_string(), &user.id);
|
||||
let record_key = get_id(&id)?;
|
||||
let record: Option<UsersSchema> = db
|
||||
.update(record_key)
|
||||
.merge(serde_json::json!({ "is_deleted": true }))
|
||||
.await?;
|
||||
match record {
|
||||
Some(_) => Ok("Success delete user".into()),
|
||||
None => bail!("Failed to delete user"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
use super::{UsersDetailItemDto, UsersListItemDto};
|
||||
use crate::RolesItemDto;
|
||||
use imphnen_utils::Crud;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use surrealdb::sql::Thing;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct UsersSchema {
|
||||
pub id: Thing,
|
||||
pub fullname: String,
|
||||
pub email: String,
|
||||
pub password: String,
|
||||
pub avatar: Option<String>,
|
||||
pub phone_number: String,
|
||||
pub is_active: bool,
|
||||
pub is_deleted: bool,
|
||||
pub gender: Option<String>,
|
||||
pub birthdate: Option<String>,
|
||||
pub role: Thing,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
impl Crud<UsersListItemDto, String> for UsersSchema {
|
||||
fn list(&self, role: String) -> UsersListItemDto {
|
||||
UsersListItemDto {
|
||||
id: self.id.id.to_raw(),
|
||||
role,
|
||||
fullname: self.fullname.clone(),
|
||||
email: self.email.clone(),
|
||||
avatar: self.avatar.clone(),
|
||||
phone_number: self.phone_number.clone(),
|
||||
is_active: self.is_active,
|
||||
created_at: self.created_at.clone(),
|
||||
updated_at: self.updated_at.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Crud<UsersDetailItemDto, RolesItemDto> for UsersSchema {
|
||||
fn detail(&self, role: RolesItemDto) -> UsersDetailItemDto {
|
||||
UsersDetailItemDto {
|
||||
id: self.id.id.to_raw(),
|
||||
role,
|
||||
fullname: self.fullname.clone(),
|
||||
email: self.email.clone(),
|
||||
avatar: self.avatar.clone(),
|
||||
phone_number: self.phone_number.clone(),
|
||||
is_active: self.is_active,
|
||||
gender: self.gender.clone(),
|
||||
birthdate: self.birthdate.clone(),
|
||||
created_at: self.created_at.clone(),
|
||||
updated_at: self.updated_at.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UsersSchema {
|
||||
pub fn list_from(&self, role: String) -> UsersListItemDto {
|
||||
self.list(role)
|
||||
}
|
||||
|
||||
pub fn detail_from(&self, role: RolesItemDto) -> UsersDetailItemDto {
|
||||
self.detail(role)
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,11 @@ edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
imphnen-entities = { path = "../imphnen-entities" }
|
||||
argon2.workspace = true
|
||||
axum.workspace = true
|
||||
tokio.workspace = true
|
||||
chrono.workspace = true
|
||||
jsonwebtoken.workspace = true
|
||||
serde.workspace = true
|
||||
argon2.workspace = true
|
||||
lettre.workspace = true
|
||||
surrealdb = { workspace = true, features = ["kv-mem"] }
|
||||
chrono.workspace = true
|
||||
surrealdb.workspace = true
|
||||
jsonwebtoken.workspace = true
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
[package]
|
||||
name = "imphnen-middleware-service"
|
||||
name = "imphnen-middleware"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
imphnen-iam-service = { version = "0.1.0", path = "../imphnen-iam-service" }
|
||||
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
|
||||
imphnen-iam = { version = "0.1.0", path = "../imphnen-iam" }
|
||||
imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" }
|
||||
imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" }
|
||||
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
|
||||
axum.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
@@ -22,6 +22,6 @@ tokio.workspace = true
|
||||
chrono.workspace = true
|
||||
anyhow.workspace = true
|
||||
tower-http.workspace = true
|
||||
futures.workspace = true
|
||||
tower.workspace = true
|
||||
utoipa-swagger-ui.workspace = true
|
||||
futures = "0.3.31"
|
||||
tower = "0.5.2"
|
||||
+1
-1
@@ -2,7 +2,7 @@ use axum::{
|
||||
Extension, extract::Request, http::StatusCode, middleware::Next,
|
||||
response::Response,
|
||||
};
|
||||
use imphnen_iam_service::{UsersItemDtoRaw, UsersRepository};
|
||||
use imphnen_iam::{UsersItemDtoRaw, UsersRepository};
|
||||
use imphnen_libs::AppState;
|
||||
use imphnen_utils::{common_response, extract_email};
|
||||
use std::convert::Infallible;
|
||||
+1
-1
@@ -3,7 +3,7 @@ use axum::{
|
||||
http::{Request, Response, StatusCode},
|
||||
};
|
||||
use futures::future::BoxFuture;
|
||||
use imphnen_iam_service::{AuthRepository, PermissionsEnum};
|
||||
use imphnen_iam::{AuthRepository, PermissionsEnum};
|
||||
use imphnen_libs::AppState;
|
||||
use imphnen_utils::{common_response, extract_email};
|
||||
use std::task::{Context, Poll};
|
||||
@@ -0,0 +1,8 @@
|
||||
pub trait Crud<T, Args> {
|
||||
fn list(&self, _args: Args) -> T {
|
||||
unimplemented!("list() is not implemented for this type")
|
||||
}
|
||||
fn detail(&self, _args: Args) -> T {
|
||||
unimplemented!("detail() is not implemented for this type")
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,20 @@ use imphnen_entities::*;
|
||||
use imphnen_libs::*;
|
||||
|
||||
pub mod bind_filter;
|
||||
pub mod crud;
|
||||
pub mod extract_email;
|
||||
pub mod generate_date;
|
||||
pub mod generate_otp;
|
||||
pub mod get_id;
|
||||
pub mod make_thing;
|
||||
pub mod mock_test;
|
||||
pub mod query_builder;
|
||||
pub mod query_list;
|
||||
pub mod response_format;
|
||||
pub mod validator;
|
||||
|
||||
pub use bind_filter::*;
|
||||
pub use crud::*;
|
||||
pub use extract_email::*;
|
||||
pub use generate_date::*;
|
||||
pub use generate_otp::*;
|
||||
@@ -21,6 +24,7 @@ pub use imphnen_entities::*;
|
||||
pub use imphnen_libs::*;
|
||||
pub use make_thing::*;
|
||||
pub use mock_test::*;
|
||||
pub use query_builder::*;
|
||||
pub use query_list::*;
|
||||
pub use response_format::*;
|
||||
pub use validator::*;
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
use imphnen_libs::MetaRequestDto;
|
||||
|
||||
pub struct ListQueryBuilder {
|
||||
resource: String,
|
||||
conditions: Vec<String>,
|
||||
limit: usize,
|
||||
start: usize,
|
||||
order_by: Option<String>,
|
||||
order: Option<String>,
|
||||
fetch: Vec<String>,
|
||||
select_fields: Vec<String>,
|
||||
}
|
||||
|
||||
impl ListQueryBuilder {
|
||||
pub fn from_meta(
|
||||
resource: impl Into<String>,
|
||||
meta: &MetaRequestDto,
|
||||
search_field: impl Into<String>,
|
||||
select_fields: Option<Vec<&str>>,
|
||||
) -> Self {
|
||||
let mut builder = Self::new(resource)
|
||||
.with_search(meta.search.as_deref(), &search_field.into())
|
||||
.with_filter(meta.filter_by.as_deref(), meta.filter.as_deref())
|
||||
.with_sorting(meta.sort_by.as_deref(), meta.order.as_deref())
|
||||
.with_pagination(meta.page, meta.per_page);
|
||||
|
||||
if let Some(fields) = select_fields {
|
||||
builder = builder.with_select_fields(fields);
|
||||
}
|
||||
builder
|
||||
}
|
||||
|
||||
pub fn new(resource: impl Into<String>) -> Self {
|
||||
Self {
|
||||
resource: resource.into(),
|
||||
conditions: vec!["is_deleted = false".into()],
|
||||
limit: 10,
|
||||
start: 0,
|
||||
order_by: None,
|
||||
order: None,
|
||||
fetch: vec![],
|
||||
select_fields: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_select_fields(mut self, fields: Vec<&str>) -> Self {
|
||||
self.select_fields = fields.into_iter().map(String::from).collect();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_search(mut self, search: Option<&str>, field: &str) -> Self {
|
||||
if let Some(search) = search {
|
||||
if !search.is_empty() {
|
||||
self
|
||||
.conditions
|
||||
.push(format!("string::contains({} ?? '', $search)", field));
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_filter(mut self, field: Option<&str>, value: Option<&str>) -> Self {
|
||||
if let (Some(f), Some(v)) = (field, value) {
|
||||
if !v.is_empty() {
|
||||
self.conditions.push(format!("{} = $filter", f));
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_pagination(
|
||||
mut self,
|
||||
page: Option<u64>,
|
||||
per_page: Option<u64>,
|
||||
) -> Self {
|
||||
let limit = per_page.unwrap_or(10).max(1);
|
||||
let page = page.unwrap_or(1).max(1);
|
||||
self.limit = limit as usize;
|
||||
self.start = ((page - 1) * limit) as usize;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_sorting(mut self, sort_by: Option<&str>, order: Option<&str>) -> Self {
|
||||
self.order_by = sort_by.map(|s| s.to_string());
|
||||
self.order = order.map(|o| o.to_uppercase());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_fetch(mut self, fetch: impl Into<String>) -> Self {
|
||||
self.fetch.push(fetch.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> String {
|
||||
let where_clause = if !self.conditions.is_empty() {
|
||||
format!("WHERE {}", self.conditions.join(" AND "))
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let order_clause = if let Some(field) = self.order_by {
|
||||
let ord = self.order.unwrap_or_else(|| "ASC".into());
|
||||
format!("ORDER BY {} {}", field, ord)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let fetch_clause = if !self.fetch.is_empty() {
|
||||
format!("FETCH {}", self.fetch.join(", "))
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let select_clause = if self.select_fields.is_empty() {
|
||||
"*".to_string()
|
||||
} else {
|
||||
self.select_fields.join(", ")
|
||||
};
|
||||
|
||||
format!(
|
||||
r#"
|
||||
SELECT {} FROM {}
|
||||
{}
|
||||
{}
|
||||
LIMIT {} START {}
|
||||
{}
|
||||
"#,
|
||||
select_clause,
|
||||
self.resource,
|
||||
where_clause,
|
||||
order_clause,
|
||||
self.limit,
|
||||
self.start,
|
||||
fetch_clause
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
use super::bind_filter_value;
|
||||
use super::{ListQueryBuilder, bind_filter_value};
|
||||
use crate::{CountResult, MetaRequestDto, MetaResponseDto, ResponseListSuccessDto};
|
||||
use anyhow::{bail, Result};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use surrealdb::{engine::remote::ws::Client, Surreal};
|
||||
use anyhow::Result;
|
||||
use serde::{Serialize, de::DeserializeOwned};
|
||||
use surrealdb::{Surreal, engine::remote::ws::Client};
|
||||
|
||||
pub async fn query_list_with_meta<T>(
|
||||
db: &Surreal<Client>,
|
||||
@@ -10,75 +10,61 @@ pub async fn query_list_with_meta<T>(
|
||||
meta: &MetaRequestDto,
|
||||
conditions: Vec<String>,
|
||||
custom_select: Option<String>,
|
||||
search_field: &str,
|
||||
select_fields: Option<Vec<&str>>,
|
||||
) -> Result<ResponseListSuccessDto<Vec<T>>>
|
||||
where
|
||||
T: DeserializeOwned + Serialize,
|
||||
{
|
||||
let page = meta.page.unwrap_or(1);
|
||||
let per_page = meta.per_page.unwrap_or(10);
|
||||
if page < 1 || per_page < 1 {
|
||||
bail!("Invalid pagination: page and per_page must be greater than 0");
|
||||
}
|
||||
let page = meta.page.unwrap_or(1).max(1);
|
||||
let per_page = meta.per_page.unwrap_or(10).max(1);
|
||||
let start = (page - 1) * per_page;
|
||||
|
||||
let sql = custom_select.unwrap_or_else(|| {
|
||||
let mut s = format!("SELECT * FROM {}", table);
|
||||
if !conditions.is_empty() {
|
||||
s.push_str(" WHERE ");
|
||||
s.push_str(&conditions.join(" AND "));
|
||||
}
|
||||
if let Some(sort_by) = &meta.sort_by {
|
||||
let order = match meta
|
||||
.order
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.to_uppercase()
|
||||
.as_str()
|
||||
{
|
||||
"DESC" => "DESC",
|
||||
_ => "ASC",
|
||||
};
|
||||
s.push_str(&format!(" ORDER BY {} {}", sort_by, order));
|
||||
}
|
||||
s.push_str(" LIMIT $per_page START $start");
|
||||
s
|
||||
ListQueryBuilder::from_meta(table, meta, search_field, select_fields).build()
|
||||
});
|
||||
|
||||
let mut query_exec = db.query(sql);
|
||||
if let Some(search) = &meta.search {
|
||||
if !search.is_empty() {
|
||||
query_exec = query_exec.bind(("search", search.clone()));
|
||||
}
|
||||
}
|
||||
if let Some(filter_val) = meta.filter.clone() {
|
||||
query_exec = bind_filter_value(query_exec, filter_val);
|
||||
if let Some(filter_val) = &meta.filter {
|
||||
query_exec = bind_filter_value(query_exec, filter_val.clone());
|
||||
}
|
||||
query_exec = query_exec
|
||||
.bind(("per_page", per_page))
|
||||
.bind(("start", start));
|
||||
|
||||
let raw: Vec<T> = query_exec.await?.take(0)?;
|
||||
let mut count_sql = format!("SELECT count() FROM {}", table);
|
||||
if !conditions.is_empty() {
|
||||
count_sql.push_str(" WHERE ");
|
||||
count_sql.push_str(&conditions.join(" AND "));
|
||||
}
|
||||
let mut count_query = db.query(count_sql);
|
||||
|
||||
let mut count_query = db.query(format!(
|
||||
"SELECT count() FROM {} {}",
|
||||
table,
|
||||
if conditions.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!("WHERE {}", conditions.join(" AND "))
|
||||
}
|
||||
));
|
||||
if let Some(search) = &meta.search {
|
||||
if !search.is_empty() {
|
||||
count_query = count_query.bind(("search", search.clone()));
|
||||
}
|
||||
}
|
||||
if let Some(filter_val) = meta.filter.clone() {
|
||||
count_query = bind_filter_value(count_query, filter_val);
|
||||
if let Some(filter_val) = &meta.filter {
|
||||
count_query = bind_filter_value(count_query, filter_val.clone());
|
||||
}
|
||||
let count_result: Vec<CountResult> = count_query.await?.take(0)?;
|
||||
let total = count_result.first().map(|c| c.count);
|
||||
|
||||
let meta = MetaResponseDto {
|
||||
page: Some(page),
|
||||
per_page: Some(per_page),
|
||||
total,
|
||||
};
|
||||
Ok(ResponseListSuccessDto {
|
||||
data: raw,
|
||||
meta: Some(meta),
|
||||
meta: Some(MetaResponseDto {
|
||||
page: Some(page),
|
||||
per_page: Some(per_page),
|
||||
total,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
+1
-1
@@ -4,10 +4,10 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
imphnen-iam = { version = "0.1.0", path = "../imphnen-iam" }
|
||||
imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" }
|
||||
imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" }
|
||||
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
|
||||
imphnen-iam-service = { version = "0.1.0", path = "../imphnen-iam-service" }
|
||||
axum.workspace = true
|
||||
axum-test.workspace = true
|
||||
chrono.workspace = true
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user