From a43ef8ec044e5afcacc83ee53796b72ad5eeb083 Mon Sep 17 00:00:00 2001 From: Maulana Sodiqin Date: Sat, 17 May 2025 02:08:59 +0700 Subject: [PATCH 1/4] chore: normalize code --- Cargo.lock | 46 +-- Cargo.toml | 22 +- .../Cargo.toml | 4 +- .../src/bin/api.rs | 2 +- .../src/bin/seed_permissions.rs | 2 +- .../src/bin/seed_roles.rs | 2 +- .../src/bin/seed_roles_permissions.rs | 28 +- .../src/bin/seed_users.rs | 2 +- .../src/main.rs | 2 +- imphnen-dimentorin-service/src/lib.rs | 14 - .../Cargo.toml | 6 +- .../src/lib.rs | 0 .../src/v1/analytics/.gitkeep | 0 .../src/v1/articles/.gitkeep | 0 .../src/v1/courses/.gitkeep | 0 .../src/v2/.gitkeep | 0 imphnen-entities/Cargo.toml | 4 +- .../Cargo.toml | 6 +- .../src/lib.rs | 0 .../v1/gacha_claim/gacha_claim_controller.rs | 0 .../src/v1/gacha_claim/gacha_claim_dto.rs | 0 .../v1/gacha_claim/gacha_claim_repository.rs | 0 .../src/v1/gacha_claim/gacha_claim_schema.rs | 0 .../src/v1/gacha_claim/gacha_claim_service.rs | 0 .../src/v1/gacha_claim/mod.rs | 0 .../v1/gacha_item/gacha_item_controller.rs | 0 .../src/v1/gacha_item/gacha_item_dto.rs | 0 .../v1/gacha_item/gacha_item_repository.rs | 0 .../src/v1/gacha_item/gacha_item_schema.rs | 0 .../src/v1/gacha_item/gacha_item_service.rs | 0 .../src/v1/gacha_item/mod.rs | 0 .../v1/gacha_roll/gacha_roll_controller.rs | 0 .../src/v1/gacha_roll/gacha_roll_dto.rs | 0 .../v1/gacha_roll/gacha_roll_repository.rs | 0 .../src/v1/gacha_roll/gacha_roll_schema.rs | 0 .../src/v1/gacha_roll/gacha_roll_service.rs | 0 .../src/v1/gacha_roll/mod.rs | 0 .../src/v1/mod.rs | 0 .../src/v2/.gitkeep | 0 imphnen-gateway-service/Cargo.toml | 26 -- .../Cargo.toml | 8 +- .../src/docs.rs | 4 +- .../src/lib.rs | 6 +- imphnen-iam-service/src/v1/roles/roles_dto.rs | 66 ---- .../src/v1/users/users_repository.rs | 317 ------------------ .../src/v1/users/users_schema.rs | 56 ---- .../Cargo.toml | 6 +- .../src/lib.rs | 4 +- .../src/v1/auth/auth_controller.rs | 0 .../src/v1/auth/auth_dto.rs | 0 .../src/v1/auth/auth_repository.rs | 0 .../src/v1/auth/auth_schema.rs | 0 .../src/v1/auth/auth_service.rs | 0 .../src/v1/auth/mod.rs | 0 .../src/v1/mod.rs | 0 .../src/v1/permissions/mod.rs | 0 .../v1/permissions/permissions_controller.rs | 0 .../src/v1/permissions/permissions_dto.rs | 2 +- .../src/v1/permissions/permissions_enum.rs | 0 .../src/v1/permissions/permissions_guard.rs | 0 .../v1/permissions/permissions_repository.rs | 0 .../src/v1/permissions/permissions_schema.rs | 0 .../src/v1/permissions/permissions_service.rs | 0 .../src/v1/roles/mod.rs | 0 .../src/v1/roles/roles_controller.rs | 0 imphnen-iam/src/v1/roles/roles_dto.rs | 40 +++ .../src/v1/roles/roles_enum.rs | 0 .../src/v1/roles/roles_repository.rs | 0 .../src/v1/roles/roles_schema.rs | 0 .../src/v1/roles/roles_service.rs | 0 .../src/v1/users/mod.rs | 0 .../src/v1/users/users_controller.rs | 0 .../src/v1/users/users_dto.rs | 54 ++- imphnen-iam/src/v1/users/users_repository.rs | 296 ++++++++++++++++ imphnen-iam/src/v1/users/users_schema.rs | 66 ++++ .../src/v1/users/users_service.rs | 0 .../src/v2/.gitkeep | 0 imphnen-libs/Cargo.toml | 8 +- .../Cargo.toml | 10 +- .../src/auth_middleware/mod.rs | 2 +- .../src/cors_middleware/mod.rs | 0 .../src/lib.rs | 0 .../src/payment_middleware/.gitkeep | 0 .../src/permissions_middleware/mod.rs | 2 +- imphnen-utils/Cargo.toml | 2 +- imphnen-utils/src/crud.rs | 8 + imphnen-utils/src/lib.rs | 4 + imphnen-utils/src/query_builder.rs | 137 ++++++++ imphnen-utils/src/query_list.rs | 76 ++--- tests/Cargo.toml | 2 +- .../auth/auth_controller_test.rs | 0 .../auth/auth_repository_test.rs | 0 tests/src/{iam_service => iam}/auth/mod.rs | 0 tests/src/{iam_service => iam}/mod.rs | 0 .../{iam_service => iam}/permissions/mod.rs | 0 .../permissions_controller_test.rs | 0 .../permissions_repository_test.rs | 0 tests/src/{iam_service => iam}/roles/mod.rs | 0 .../roles/roles_controller_test.rs | 0 .../roles/roles_repository_test.rs | 0 tests/src/{iam_service => iam}/users/mod.rs | 0 .../users/users_controller_test.rs | 0 .../users/users_repository_test.rs | 0 tests/src/lib.rs | 4 +- 104 files changed, 691 insertions(+), 655 deletions(-) rename {imphnen-core-service => imphnen-backend}/Cargo.toml (84%) rename {imphnen-core-service => imphnen-backend}/src/bin/api.rs (79%) rename {imphnen-core-service => imphnen-backend}/src/bin/seed_permissions.rs (98%) rename {imphnen-core-service => imphnen-backend}/src/bin/seed_roles.rs (96%) rename {imphnen-core-service => imphnen-backend}/src/bin/seed_roles_permissions.rs (55%) rename {imphnen-core-service => imphnen-backend}/src/bin/seed_users.rs (97%) rename {imphnen-core-service => imphnen-backend}/src/main.rs (79%) delete mode 100644 imphnen-dimentorin-service/src/lib.rs rename {imphnen-cms-service => imphnen-dimentorin}/Cargo.toml (90%) rename {imphnen-cms-service => imphnen-dimentorin}/src/lib.rs (100%) rename {imphnen-dimentorin-service => imphnen-dimentorin}/src/v1/analytics/.gitkeep (100%) rename {imphnen-dimentorin-service => imphnen-dimentorin}/src/v1/articles/.gitkeep (100%) rename {imphnen-dimentorin-service => imphnen-dimentorin}/src/v1/courses/.gitkeep (100%) rename {imphnen-dimentorin-service => imphnen-dimentorin}/src/v2/.gitkeep (100%) rename {imphnen-iam-service => imphnen-gacha}/Cargo.toml (90%) rename {imphnen-gacha-service => imphnen-gacha}/src/lib.rs (100%) rename {imphnen-gacha-service => imphnen-gacha}/src/v1/gacha_claim/gacha_claim_controller.rs (100%) rename {imphnen-gacha-service => imphnen-gacha}/src/v1/gacha_claim/gacha_claim_dto.rs (100%) rename {imphnen-gacha-service => imphnen-gacha}/src/v1/gacha_claim/gacha_claim_repository.rs (100%) rename {imphnen-gacha-service => imphnen-gacha}/src/v1/gacha_claim/gacha_claim_schema.rs (100%) rename {imphnen-gacha-service => imphnen-gacha}/src/v1/gacha_claim/gacha_claim_service.rs (100%) rename {imphnen-gacha-service => imphnen-gacha}/src/v1/gacha_claim/mod.rs (100%) rename {imphnen-gacha-service => imphnen-gacha}/src/v1/gacha_item/gacha_item_controller.rs (100%) rename {imphnen-gacha-service => imphnen-gacha}/src/v1/gacha_item/gacha_item_dto.rs (100%) rename {imphnen-gacha-service => imphnen-gacha}/src/v1/gacha_item/gacha_item_repository.rs (100%) rename {imphnen-gacha-service => imphnen-gacha}/src/v1/gacha_item/gacha_item_schema.rs (100%) rename {imphnen-gacha-service => imphnen-gacha}/src/v1/gacha_item/gacha_item_service.rs (100%) rename {imphnen-gacha-service => imphnen-gacha}/src/v1/gacha_item/mod.rs (100%) rename {imphnen-gacha-service => imphnen-gacha}/src/v1/gacha_roll/gacha_roll_controller.rs (100%) rename {imphnen-gacha-service => imphnen-gacha}/src/v1/gacha_roll/gacha_roll_dto.rs (100%) rename {imphnen-gacha-service => imphnen-gacha}/src/v1/gacha_roll/gacha_roll_repository.rs (100%) rename {imphnen-gacha-service => imphnen-gacha}/src/v1/gacha_roll/gacha_roll_schema.rs (100%) rename {imphnen-gacha-service => imphnen-gacha}/src/v1/gacha_roll/gacha_roll_service.rs (100%) rename {imphnen-gacha-service => imphnen-gacha}/src/v1/gacha_roll/mod.rs (100%) rename {imphnen-gacha-service => imphnen-gacha}/src/v1/mod.rs (100%) rename {imphnen-gacha-service => imphnen-gacha}/src/v2/.gitkeep (100%) delete mode 100644 imphnen-gateway-service/Cargo.toml rename {imphnen-dimentorin-service => imphnen-gateway}/Cargo.toml (75%) rename {imphnen-gateway-service => imphnen-gateway}/src/docs.rs (99%) rename {imphnen-gateway-service => imphnen-gateway}/src/lib.rs (84%) delete mode 100644 imphnen-iam-service/src/v1/roles/roles_dto.rs delete mode 100644 imphnen-iam-service/src/v1/users/users_repository.rs delete mode 100644 imphnen-iam-service/src/v1/users/users_schema.rs rename {imphnen-gacha-service => imphnen-iam}/Cargo.toml (90%) rename {imphnen-iam-service => imphnen-iam}/src/lib.rs (88%) rename {imphnen-iam-service => imphnen-iam}/src/v1/auth/auth_controller.rs (100%) rename {imphnen-iam-service => imphnen-iam}/src/v1/auth/auth_dto.rs (100%) rename {imphnen-iam-service => imphnen-iam}/src/v1/auth/auth_repository.rs (100%) rename {imphnen-iam-service => imphnen-iam}/src/v1/auth/auth_schema.rs (100%) rename {imphnen-iam-service => imphnen-iam}/src/v1/auth/auth_service.rs (100%) rename {imphnen-iam-service => imphnen-iam}/src/v1/auth/mod.rs (100%) rename {imphnen-iam-service => imphnen-iam}/src/v1/mod.rs (100%) rename {imphnen-iam-service => imphnen-iam}/src/v1/permissions/mod.rs (100%) rename {imphnen-iam-service => imphnen-iam}/src/v1/permissions/permissions_controller.rs (100%) rename {imphnen-iam-service => imphnen-iam}/src/v1/permissions/permissions_dto.rs (94%) rename {imphnen-iam-service => imphnen-iam}/src/v1/permissions/permissions_enum.rs (100%) rename {imphnen-iam-service => imphnen-iam}/src/v1/permissions/permissions_guard.rs (100%) rename {imphnen-iam-service => imphnen-iam}/src/v1/permissions/permissions_repository.rs (100%) rename {imphnen-iam-service => imphnen-iam}/src/v1/permissions/permissions_schema.rs (100%) rename {imphnen-iam-service => imphnen-iam}/src/v1/permissions/permissions_service.rs (100%) rename {imphnen-iam-service => imphnen-iam}/src/v1/roles/mod.rs (100%) rename {imphnen-iam-service => imphnen-iam}/src/v1/roles/roles_controller.rs (100%) create mode 100644 imphnen-iam/src/v1/roles/roles_dto.rs rename {imphnen-iam-service => imphnen-iam}/src/v1/roles/roles_enum.rs (100%) rename {imphnen-iam-service => imphnen-iam}/src/v1/roles/roles_repository.rs (100%) rename {imphnen-iam-service => imphnen-iam}/src/v1/roles/roles_schema.rs (100%) rename {imphnen-iam-service => imphnen-iam}/src/v1/roles/roles_service.rs (100%) rename {imphnen-iam-service => imphnen-iam}/src/v1/users/mod.rs (100%) rename {imphnen-iam-service => imphnen-iam}/src/v1/users/users_controller.rs (100%) rename {imphnen-iam-service => imphnen-iam}/src/v1/users/users_dto.rs (83%) create mode 100644 imphnen-iam/src/v1/users/users_repository.rs create mode 100644 imphnen-iam/src/v1/users/users_schema.rs rename {imphnen-iam-service => imphnen-iam}/src/v1/users/users_service.rs (100%) rename {imphnen-iam-service => imphnen-iam}/src/v2/.gitkeep (100%) rename {imphnen-middleware-service => imphnen-middleware}/Cargo.toml (81%) rename {imphnen-middleware-service => imphnen-middleware}/src/auth_middleware/mod.rs (94%) rename {imphnen-middleware-service => imphnen-middleware}/src/cors_middleware/mod.rs (100%) rename {imphnen-middleware-service => imphnen-middleware}/src/lib.rs (100%) rename {imphnen-middleware-service => imphnen-middleware}/src/payment_middleware/.gitkeep (100%) rename {imphnen-middleware-service => imphnen-middleware}/src/permissions_middleware/mod.rs (97%) create mode 100644 imphnen-utils/src/crud.rs create mode 100644 imphnen-utils/src/query_builder.rs rename tests/src/{iam_service => iam}/auth/auth_controller_test.rs (100%) rename tests/src/{iam_service => iam}/auth/auth_repository_test.rs (100%) rename tests/src/{iam_service => iam}/auth/mod.rs (100%) rename tests/src/{iam_service => iam}/mod.rs (100%) rename tests/src/{iam_service => iam}/permissions/mod.rs (100%) rename tests/src/{iam_service => iam}/permissions/permissions_controller_test.rs (100%) rename tests/src/{iam_service => iam}/permissions/permissions_repository_test.rs (100%) rename tests/src/{iam_service => iam}/roles/mod.rs (100%) rename tests/src/{iam_service => iam}/roles/roles_controller_test.rs (100%) rename tests/src/{iam_service => iam}/roles/roles_repository_test.rs (100%) rename tests/src/{iam_service => iam}/users/mod.rs (100%) rename tests/src/{iam_service => iam}/users/users_controller_test.rs (100%) rename tests/src/{iam_service => iam}/users/users_repository_test.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 53d06e7..48f9341 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index 012ea8e..58e6c7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/imphnen-core-service/Cargo.toml b/imphnen-backend/Cargo.toml similarity index 84% rename from imphnen-core-service/Cargo.toml rename to imphnen-backend/Cargo.toml index 7ec3bb8..0409d39 100644 --- a/imphnen-core-service/Cargo.toml +++ b/imphnen-backend/Cargo.toml @@ -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 diff --git a/imphnen-core-service/src/bin/api.rs b/imphnen-backend/src/bin/api.rs similarity index 79% rename from imphnen-core-service/src/bin/api.rs rename to imphnen-backend/src/bin/api.rs index 7d654e2..a261ef9 100644 --- a/imphnen-core-service/src/bin/api.rs +++ b/imphnen-backend/src/bin/api.rs @@ -1,4 +1,4 @@ -use imphnen_gateway_service::gateway_service; +use imphnen_gateway::gateway_service; use imphnen_libs::axum_init; #[tokio::main] diff --git a/imphnen-core-service/src/bin/seed_permissions.rs b/imphnen-backend/src/bin/seed_permissions.rs similarity index 98% rename from imphnen-core-service/src/bin/seed_permissions.rs rename to imphnen-backend/src/bin/seed_permissions.rs index 53e4660..3f878d8 100644 --- a/imphnen-core-service/src/bin/seed_permissions.rs +++ b/imphnen-backend/src/bin/seed_permissions.rs @@ -140,6 +140,6 @@ async fn main() -> Result<(), Box> { .await?; println!("✅ Inserted: {}", name); } - println!("✅ Semua permissions berhasil disimpan ke SurrealDB!"); + println!("✅ All Permissions seeded"); Ok(()) } diff --git a/imphnen-core-service/src/bin/seed_roles.rs b/imphnen-backend/src/bin/seed_roles.rs similarity index 96% rename from imphnen-core-service/src/bin/seed_roles.rs rename to imphnen-backend/src/bin/seed_roles.rs index b01af23..5ca1aec 100644 --- a/imphnen-core-service/src/bin/seed_roles.rs +++ b/imphnen-backend/src/bin/seed_roles.rs @@ -65,6 +65,6 @@ async fn main() -> Result<(), Box> { .await?; println!("✅ Inserted role: {}", name); } - println!("✅ Semua role berhasil disimpan ke SurrealDB!"); + println!("✅ All Roles seeded"); Ok(()) } diff --git a/imphnen-core-service/src/bin/seed_roles_permissions.rs b/imphnen-backend/src/bin/seed_roles_permissions.rs similarity index 55% rename from imphnen-core-service/src/bin/seed_roles_permissions.rs rename to imphnen-backend/src/bin/seed_roles_permissions.rs index 816962d..71466d5 100644 --- a/imphnen-core-service/src/bin/seed_roles_permissions.rs +++ b/imphnen-backend/src/bin/seed_roles_permissions.rs @@ -19,24 +19,24 @@ async fn main() -> Result<(), Box> { 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> { .await?; println!("✅ Updated permissions for role ID: {}", role_id); } - println!("✅ Semua roles telah diperbarui dengan permissions di SurrealDB!"); + println!("✅ All Roles permissions seeded"); Ok(()) } diff --git a/imphnen-core-service/src/bin/seed_users.rs b/imphnen-backend/src/bin/seed_users.rs similarity index 97% rename from imphnen-core-service/src/bin/seed_users.rs rename to imphnen-backend/src/bin/seed_users.rs index 7318d79..01426bc 100644 --- a/imphnen-core-service/src/bin/seed_users.rs +++ b/imphnen-backend/src/bin/seed_users.rs @@ -90,6 +90,6 @@ async fn main() -> Result<(), Box> { println!("✅ Inserted user: {} ({})", fullname, email); } - println!("✅ Semua users berhasil disimpan ke SurrealDB!"); + println!("✅ All Users seeded"); Ok(()) } diff --git a/imphnen-core-service/src/main.rs b/imphnen-backend/src/main.rs similarity index 79% rename from imphnen-core-service/src/main.rs rename to imphnen-backend/src/main.rs index 7d654e2..a261ef9 100644 --- a/imphnen-core-service/src/main.rs +++ b/imphnen-backend/src/main.rs @@ -1,4 +1,4 @@ -use imphnen_gateway_service::gateway_service; +use imphnen_gateway::gateway_service; use imphnen_libs::axum_init; #[tokio::main] diff --git a/imphnen-dimentorin-service/src/lib.rs b/imphnen-dimentorin-service/src/lib.rs deleted file mode 100644 index 1303f3d..0000000 --- a/imphnen-dimentorin-service/src/lib.rs +++ /dev/null @@ -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); - } -} diff --git a/imphnen-cms-service/Cargo.toml b/imphnen-dimentorin/Cargo.toml similarity index 90% rename from imphnen-cms-service/Cargo.toml rename to imphnen-dimentorin/Cargo.toml index c2d0734..6b4bce3 100644 --- a/imphnen-cms-service/Cargo.toml +++ b/imphnen-dimentorin/Cargo.toml @@ -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 @@ -21,4 +21,4 @@ tokio.workspace = true chrono.workspace = true anyhow.workspace = true tower-http.workspace = true -utoipa-swagger-ui.workspace = true \ No newline at end of file +utoipa-swagger-ui.workspace = true diff --git a/imphnen-cms-service/src/lib.rs b/imphnen-dimentorin/src/lib.rs similarity index 100% rename from imphnen-cms-service/src/lib.rs rename to imphnen-dimentorin/src/lib.rs diff --git a/imphnen-dimentorin-service/src/v1/analytics/.gitkeep b/imphnen-dimentorin/src/v1/analytics/.gitkeep similarity index 100% rename from imphnen-dimentorin-service/src/v1/analytics/.gitkeep rename to imphnen-dimentorin/src/v1/analytics/.gitkeep diff --git a/imphnen-dimentorin-service/src/v1/articles/.gitkeep b/imphnen-dimentorin/src/v1/articles/.gitkeep similarity index 100% rename from imphnen-dimentorin-service/src/v1/articles/.gitkeep rename to imphnen-dimentorin/src/v1/articles/.gitkeep diff --git a/imphnen-dimentorin-service/src/v1/courses/.gitkeep b/imphnen-dimentorin/src/v1/courses/.gitkeep similarity index 100% rename from imphnen-dimentorin-service/src/v1/courses/.gitkeep rename to imphnen-dimentorin/src/v1/courses/.gitkeep diff --git a/imphnen-dimentorin-service/src/v2/.gitkeep b/imphnen-dimentorin/src/v2/.gitkeep similarity index 100% rename from imphnen-dimentorin-service/src/v2/.gitkeep rename to imphnen-dimentorin/src/v2/.gitkeep diff --git a/imphnen-entities/Cargo.toml b/imphnen-entities/Cargo.toml index bce60d5..6db179b 100644 --- a/imphnen-entities/Cargo.toml +++ b/imphnen-entities/Cargo.toml @@ -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 diff --git a/imphnen-iam-service/Cargo.toml b/imphnen-gacha/Cargo.toml similarity index 90% rename from imphnen-iam-service/Cargo.toml rename to imphnen-gacha/Cargo.toml index 4edeaac..a4ea4e9 100644 --- a/imphnen-iam-service/Cargo.toml +++ b/imphnen-gacha/Cargo.toml @@ -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 @@ -21,4 +21,4 @@ tokio.workspace = true chrono.workspace = true anyhow.workspace = true tower-http.workspace = true -utoipa-swagger-ui.workspace = true \ No newline at end of file +utoipa-swagger-ui.workspace = true diff --git a/imphnen-gacha-service/src/lib.rs b/imphnen-gacha/src/lib.rs similarity index 100% rename from imphnen-gacha-service/src/lib.rs rename to imphnen-gacha/src/lib.rs diff --git a/imphnen-gacha-service/src/v1/gacha_claim/gacha_claim_controller.rs b/imphnen-gacha/src/v1/gacha_claim/gacha_claim_controller.rs similarity index 100% rename from imphnen-gacha-service/src/v1/gacha_claim/gacha_claim_controller.rs rename to imphnen-gacha/src/v1/gacha_claim/gacha_claim_controller.rs diff --git a/imphnen-gacha-service/src/v1/gacha_claim/gacha_claim_dto.rs b/imphnen-gacha/src/v1/gacha_claim/gacha_claim_dto.rs similarity index 100% rename from imphnen-gacha-service/src/v1/gacha_claim/gacha_claim_dto.rs rename to imphnen-gacha/src/v1/gacha_claim/gacha_claim_dto.rs diff --git a/imphnen-gacha-service/src/v1/gacha_claim/gacha_claim_repository.rs b/imphnen-gacha/src/v1/gacha_claim/gacha_claim_repository.rs similarity index 100% rename from imphnen-gacha-service/src/v1/gacha_claim/gacha_claim_repository.rs rename to imphnen-gacha/src/v1/gacha_claim/gacha_claim_repository.rs diff --git a/imphnen-gacha-service/src/v1/gacha_claim/gacha_claim_schema.rs b/imphnen-gacha/src/v1/gacha_claim/gacha_claim_schema.rs similarity index 100% rename from imphnen-gacha-service/src/v1/gacha_claim/gacha_claim_schema.rs rename to imphnen-gacha/src/v1/gacha_claim/gacha_claim_schema.rs diff --git a/imphnen-gacha-service/src/v1/gacha_claim/gacha_claim_service.rs b/imphnen-gacha/src/v1/gacha_claim/gacha_claim_service.rs similarity index 100% rename from imphnen-gacha-service/src/v1/gacha_claim/gacha_claim_service.rs rename to imphnen-gacha/src/v1/gacha_claim/gacha_claim_service.rs diff --git a/imphnen-gacha-service/src/v1/gacha_claim/mod.rs b/imphnen-gacha/src/v1/gacha_claim/mod.rs similarity index 100% rename from imphnen-gacha-service/src/v1/gacha_claim/mod.rs rename to imphnen-gacha/src/v1/gacha_claim/mod.rs diff --git a/imphnen-gacha-service/src/v1/gacha_item/gacha_item_controller.rs b/imphnen-gacha/src/v1/gacha_item/gacha_item_controller.rs similarity index 100% rename from imphnen-gacha-service/src/v1/gacha_item/gacha_item_controller.rs rename to imphnen-gacha/src/v1/gacha_item/gacha_item_controller.rs diff --git a/imphnen-gacha-service/src/v1/gacha_item/gacha_item_dto.rs b/imphnen-gacha/src/v1/gacha_item/gacha_item_dto.rs similarity index 100% rename from imphnen-gacha-service/src/v1/gacha_item/gacha_item_dto.rs rename to imphnen-gacha/src/v1/gacha_item/gacha_item_dto.rs diff --git a/imphnen-gacha-service/src/v1/gacha_item/gacha_item_repository.rs b/imphnen-gacha/src/v1/gacha_item/gacha_item_repository.rs similarity index 100% rename from imphnen-gacha-service/src/v1/gacha_item/gacha_item_repository.rs rename to imphnen-gacha/src/v1/gacha_item/gacha_item_repository.rs diff --git a/imphnen-gacha-service/src/v1/gacha_item/gacha_item_schema.rs b/imphnen-gacha/src/v1/gacha_item/gacha_item_schema.rs similarity index 100% rename from imphnen-gacha-service/src/v1/gacha_item/gacha_item_schema.rs rename to imphnen-gacha/src/v1/gacha_item/gacha_item_schema.rs diff --git a/imphnen-gacha-service/src/v1/gacha_item/gacha_item_service.rs b/imphnen-gacha/src/v1/gacha_item/gacha_item_service.rs similarity index 100% rename from imphnen-gacha-service/src/v1/gacha_item/gacha_item_service.rs rename to imphnen-gacha/src/v1/gacha_item/gacha_item_service.rs diff --git a/imphnen-gacha-service/src/v1/gacha_item/mod.rs b/imphnen-gacha/src/v1/gacha_item/mod.rs similarity index 100% rename from imphnen-gacha-service/src/v1/gacha_item/mod.rs rename to imphnen-gacha/src/v1/gacha_item/mod.rs diff --git a/imphnen-gacha-service/src/v1/gacha_roll/gacha_roll_controller.rs b/imphnen-gacha/src/v1/gacha_roll/gacha_roll_controller.rs similarity index 100% rename from imphnen-gacha-service/src/v1/gacha_roll/gacha_roll_controller.rs rename to imphnen-gacha/src/v1/gacha_roll/gacha_roll_controller.rs diff --git a/imphnen-gacha-service/src/v1/gacha_roll/gacha_roll_dto.rs b/imphnen-gacha/src/v1/gacha_roll/gacha_roll_dto.rs similarity index 100% rename from imphnen-gacha-service/src/v1/gacha_roll/gacha_roll_dto.rs rename to imphnen-gacha/src/v1/gacha_roll/gacha_roll_dto.rs diff --git a/imphnen-gacha-service/src/v1/gacha_roll/gacha_roll_repository.rs b/imphnen-gacha/src/v1/gacha_roll/gacha_roll_repository.rs similarity index 100% rename from imphnen-gacha-service/src/v1/gacha_roll/gacha_roll_repository.rs rename to imphnen-gacha/src/v1/gacha_roll/gacha_roll_repository.rs diff --git a/imphnen-gacha-service/src/v1/gacha_roll/gacha_roll_schema.rs b/imphnen-gacha/src/v1/gacha_roll/gacha_roll_schema.rs similarity index 100% rename from imphnen-gacha-service/src/v1/gacha_roll/gacha_roll_schema.rs rename to imphnen-gacha/src/v1/gacha_roll/gacha_roll_schema.rs diff --git a/imphnen-gacha-service/src/v1/gacha_roll/gacha_roll_service.rs b/imphnen-gacha/src/v1/gacha_roll/gacha_roll_service.rs similarity index 100% rename from imphnen-gacha-service/src/v1/gacha_roll/gacha_roll_service.rs rename to imphnen-gacha/src/v1/gacha_roll/gacha_roll_service.rs diff --git a/imphnen-gacha-service/src/v1/gacha_roll/mod.rs b/imphnen-gacha/src/v1/gacha_roll/mod.rs similarity index 100% rename from imphnen-gacha-service/src/v1/gacha_roll/mod.rs rename to imphnen-gacha/src/v1/gacha_roll/mod.rs diff --git a/imphnen-gacha-service/src/v1/mod.rs b/imphnen-gacha/src/v1/mod.rs similarity index 100% rename from imphnen-gacha-service/src/v1/mod.rs rename to imphnen-gacha/src/v1/mod.rs diff --git a/imphnen-gacha-service/src/v2/.gitkeep b/imphnen-gacha/src/v2/.gitkeep similarity index 100% rename from imphnen-gacha-service/src/v2/.gitkeep rename to imphnen-gacha/src/v2/.gitkeep diff --git a/imphnen-gateway-service/Cargo.toml b/imphnen-gateway-service/Cargo.toml deleted file mode 100644 index 512f618..0000000 --- a/imphnen-gateway-service/Cargo.toml +++ /dev/null @@ -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 \ No newline at end of file diff --git a/imphnen-dimentorin-service/Cargo.toml b/imphnen-gateway/Cargo.toml similarity index 75% rename from imphnen-dimentorin-service/Cargo.toml rename to imphnen-gateway/Cargo.toml index 5c87db5..ae0602c 100644 --- a/imphnen-dimentorin-service/Cargo.toml +++ b/imphnen-gateway/Cargo.toml @@ -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 @@ -21,4 +23,4 @@ tokio.workspace = true chrono.workspace = true anyhow.workspace = true tower-http.workspace = true -utoipa-swagger-ui.workspace = true \ No newline at end of file +utoipa-swagger-ui.workspace = true diff --git a/imphnen-gateway-service/src/docs.rs b/imphnen-gateway/src/docs.rs similarity index 99% rename from imphnen-gateway-service/src/docs.rs rename to imphnen-gateway/src/docs.rs index 341601e..9f9ab3f 100644 --- a/imphnen-gateway-service/src/docs.rs +++ b/imphnen-gateway/src/docs.rs @@ -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 }; @@ -102,4 +102,4 @@ impl Modify for SecurityAddon { pub fn docs_router() -> utoipa::openapi::OpenApi { ApiDoc::openapi() -} \ No newline at end of file +} diff --git a/imphnen-gateway-service/src/lib.rs b/imphnen-gateway/src/lib.rs similarity index 84% rename from imphnen-gateway-service/src/lib.rs rename to imphnen-gateway/src/lib.rs index f08ddbb..98d3f74 100644 --- a/imphnen-gateway-service/src/lib.rs +++ b/imphnen-gateway/src/lib.rs @@ -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, diff --git a/imphnen-iam-service/src/v1/roles/roles_dto.rs b/imphnen-iam-service/src/v1/roles/roles_dto.rs deleted file mode 100644 index b53af14..0000000 --- a/imphnen-iam-service/src/v1/roles/roles_dto.rs +++ /dev/null @@ -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, - pub permissions: Option>, -} - -#[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, -} - -#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] -pub struct RolesItemListDto { - pub id: String, - pub name: String, - pub created_at: Option, - pub updated_at: Option, -} - -#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] -pub struct RolesItemDto { - pub id: String, - pub name: String, - pub permissions: Vec, - pub created_at: Option, - pub updated_at: Option, -} - -#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] -pub struct RolesItemByIdDto { - pub id: String, - pub name: String, - pub is_deleted: bool, - pub permissions: Vec, - pub created_at: Option, - pub updated_at: Option, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RolesItemByIdDtoRaw { - pub id: Thing, - pub name: String, - pub permissions: Vec, - pub is_deleted: bool, - pub created_at: Option, - pub updated_at: Option, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RolesItemDtoRaw { - pub id: Thing, - pub name: String, - pub permissions: Vec, - pub is_deleted: bool, - pub created_at: Option, - pub updated_at: Option, -} diff --git a/imphnen-iam-service/src/v1/users/users_repository.rs b/imphnen-iam-service/src/v1/users/users_repository.rs deleted file mode 100644 index 18d6705..0000000 --- a/imphnen-iam-service/src/v1/users/users_repository.rs +++ /dev/null @@ -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>> { - 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::( - 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::>(); - Ok(ResponseListSuccessDto { - data: transformed_data, - meta: raw_result.meta, - }) - } - - pub async fn query_user_by_email(&self, email: String) -> Result { - 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 = 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::>(); - 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 { - 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 = 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::>(); - - 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 { - let db = &self.state.surrealdb_ws; - let record: Option = 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 { - 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 = 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 { - 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 = 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 { - let db = &self.state.surrealdb_ws; - let record: Option = 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 { - let db = &self.state.surrealdb_ws; - let user = self.query_user_by_email(email).await?; - let record: Option = 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 { - 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 = 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"), - } - } -} diff --git a/imphnen-iam-service/src/v1/users/users_schema.rs b/imphnen-iam-service/src/v1/users/users_schema.rs deleted file mode 100644 index bca7755..0000000 --- a/imphnen-iam-service/src/v1/users/users_schema.rs +++ /dev/null @@ -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, - pub phone_number: String, - pub is_active: bool, - pub is_deleted: bool, - pub gender: Option, - pub birthdate: Option, - 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, -} diff --git a/imphnen-gacha-service/Cargo.toml b/imphnen-iam/Cargo.toml similarity index 90% rename from imphnen-gacha-service/Cargo.toml rename to imphnen-iam/Cargo.toml index 1122fba..6266523 100644 --- a/imphnen-gacha-service/Cargo.toml +++ b/imphnen-iam/Cargo.toml @@ -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 @@ -21,4 +21,4 @@ tokio.workspace = true chrono.workspace = true anyhow.workspace = true tower-http.workspace = true -utoipa-swagger-ui.workspace = true \ No newline at end of file +utoipa-swagger-ui.workspace = true diff --git a/imphnen-iam-service/src/lib.rs b/imphnen-iam/src/lib.rs similarity index 88% rename from imphnen-iam-service/src/lib.rs rename to imphnen-iam/src/lib.rs index 3e5bec5..3249e5f 100644 --- a/imphnen-iam-service/src/lib.rs +++ b/imphnen-iam/src/lib.rs @@ -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(), } } diff --git a/imphnen-iam-service/src/v1/auth/auth_controller.rs b/imphnen-iam/src/v1/auth/auth_controller.rs similarity index 100% rename from imphnen-iam-service/src/v1/auth/auth_controller.rs rename to imphnen-iam/src/v1/auth/auth_controller.rs diff --git a/imphnen-iam-service/src/v1/auth/auth_dto.rs b/imphnen-iam/src/v1/auth/auth_dto.rs similarity index 100% rename from imphnen-iam-service/src/v1/auth/auth_dto.rs rename to imphnen-iam/src/v1/auth/auth_dto.rs diff --git a/imphnen-iam-service/src/v1/auth/auth_repository.rs b/imphnen-iam/src/v1/auth/auth_repository.rs similarity index 100% rename from imphnen-iam-service/src/v1/auth/auth_repository.rs rename to imphnen-iam/src/v1/auth/auth_repository.rs diff --git a/imphnen-iam-service/src/v1/auth/auth_schema.rs b/imphnen-iam/src/v1/auth/auth_schema.rs similarity index 100% rename from imphnen-iam-service/src/v1/auth/auth_schema.rs rename to imphnen-iam/src/v1/auth/auth_schema.rs diff --git a/imphnen-iam-service/src/v1/auth/auth_service.rs b/imphnen-iam/src/v1/auth/auth_service.rs similarity index 100% rename from imphnen-iam-service/src/v1/auth/auth_service.rs rename to imphnen-iam/src/v1/auth/auth_service.rs diff --git a/imphnen-iam-service/src/v1/auth/mod.rs b/imphnen-iam/src/v1/auth/mod.rs similarity index 100% rename from imphnen-iam-service/src/v1/auth/mod.rs rename to imphnen-iam/src/v1/auth/mod.rs diff --git a/imphnen-iam-service/src/v1/mod.rs b/imphnen-iam/src/v1/mod.rs similarity index 100% rename from imphnen-iam-service/src/v1/mod.rs rename to imphnen-iam/src/v1/mod.rs diff --git a/imphnen-iam-service/src/v1/permissions/mod.rs b/imphnen-iam/src/v1/permissions/mod.rs similarity index 100% rename from imphnen-iam-service/src/v1/permissions/mod.rs rename to imphnen-iam/src/v1/permissions/mod.rs diff --git a/imphnen-iam-service/src/v1/permissions/permissions_controller.rs b/imphnen-iam/src/v1/permissions/permissions_controller.rs similarity index 100% rename from imphnen-iam-service/src/v1/permissions/permissions_controller.rs rename to imphnen-iam/src/v1/permissions/permissions_controller.rs diff --git a/imphnen-iam-service/src/v1/permissions/permissions_dto.rs b/imphnen-iam/src/v1/permissions/permissions_dto.rs similarity index 94% rename from imphnen-iam-service/src/v1/permissions/permissions_dto.rs rename to imphnen-iam/src/v1/permissions/permissions_dto.rs index f0e7df6..c589e21 100644 --- a/imphnen-iam-service/src/v1/permissions/permissions_dto.rs +++ b/imphnen-iam/src/v1/permissions/permissions_dto.rs @@ -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, diff --git a/imphnen-iam-service/src/v1/permissions/permissions_enum.rs b/imphnen-iam/src/v1/permissions/permissions_enum.rs similarity index 100% rename from imphnen-iam-service/src/v1/permissions/permissions_enum.rs rename to imphnen-iam/src/v1/permissions/permissions_enum.rs diff --git a/imphnen-iam-service/src/v1/permissions/permissions_guard.rs b/imphnen-iam/src/v1/permissions/permissions_guard.rs similarity index 100% rename from imphnen-iam-service/src/v1/permissions/permissions_guard.rs rename to imphnen-iam/src/v1/permissions/permissions_guard.rs diff --git a/imphnen-iam-service/src/v1/permissions/permissions_repository.rs b/imphnen-iam/src/v1/permissions/permissions_repository.rs similarity index 100% rename from imphnen-iam-service/src/v1/permissions/permissions_repository.rs rename to imphnen-iam/src/v1/permissions/permissions_repository.rs diff --git a/imphnen-iam-service/src/v1/permissions/permissions_schema.rs b/imphnen-iam/src/v1/permissions/permissions_schema.rs similarity index 100% rename from imphnen-iam-service/src/v1/permissions/permissions_schema.rs rename to imphnen-iam/src/v1/permissions/permissions_schema.rs diff --git a/imphnen-iam-service/src/v1/permissions/permissions_service.rs b/imphnen-iam/src/v1/permissions/permissions_service.rs similarity index 100% rename from imphnen-iam-service/src/v1/permissions/permissions_service.rs rename to imphnen-iam/src/v1/permissions/permissions_service.rs diff --git a/imphnen-iam-service/src/v1/roles/mod.rs b/imphnen-iam/src/v1/roles/mod.rs similarity index 100% rename from imphnen-iam-service/src/v1/roles/mod.rs rename to imphnen-iam/src/v1/roles/mod.rs diff --git a/imphnen-iam-service/src/v1/roles/roles_controller.rs b/imphnen-iam/src/v1/roles/roles_controller.rs similarity index 100% rename from imphnen-iam-service/src/v1/roles/roles_controller.rs rename to imphnen-iam/src/v1/roles/roles_controller.rs diff --git a/imphnen-iam/src/v1/roles/roles_dto.rs b/imphnen-iam/src/v1/roles/roles_dto.rs new file mode 100644 index 0000000..28dc98a --- /dev/null +++ b/imphnen-iam/src/v1/roles/roles_dto.rs @@ -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>, +} + +#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] +pub struct RolesListItemDto { + pub id: String, + pub name: String, + pub permissions_count: u64, + pub created_at: Option, + pub updated_at: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] +pub struct RolesDetailItemDto { + pub id: String, + pub name: String, + pub permissions: Vec, + pub created_at: Option, + pub updated_at: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RolesDetailQueryDto { + pub id: Thing, + pub name: String, + pub permissions: Vec, + pub is_deleted: bool, + pub created_at: Option, + pub updated_at: Option, +} diff --git a/imphnen-iam-service/src/v1/roles/roles_enum.rs b/imphnen-iam/src/v1/roles/roles_enum.rs similarity index 100% rename from imphnen-iam-service/src/v1/roles/roles_enum.rs rename to imphnen-iam/src/v1/roles/roles_enum.rs diff --git a/imphnen-iam-service/src/v1/roles/roles_repository.rs b/imphnen-iam/src/v1/roles/roles_repository.rs similarity index 100% rename from imphnen-iam-service/src/v1/roles/roles_repository.rs rename to imphnen-iam/src/v1/roles/roles_repository.rs diff --git a/imphnen-iam-service/src/v1/roles/roles_schema.rs b/imphnen-iam/src/v1/roles/roles_schema.rs similarity index 100% rename from imphnen-iam-service/src/v1/roles/roles_schema.rs rename to imphnen-iam/src/v1/roles/roles_schema.rs diff --git a/imphnen-iam-service/src/v1/roles/roles_service.rs b/imphnen-iam/src/v1/roles/roles_service.rs similarity index 100% rename from imphnen-iam-service/src/v1/roles/roles_service.rs rename to imphnen-iam/src/v1/roles/roles_service.rs diff --git a/imphnen-iam-service/src/v1/users/mod.rs b/imphnen-iam/src/v1/users/mod.rs similarity index 100% rename from imphnen-iam-service/src/v1/users/mod.rs rename to imphnen-iam/src/v1/users/mod.rs diff --git a/imphnen-iam-service/src/v1/users/users_controller.rs b/imphnen-iam/src/v1/users/users_controller.rs similarity index 100% rename from imphnen-iam-service/src/v1/users/users_controller.rs rename to imphnen-iam/src/v1/users/users_controller.rs diff --git a/imphnen-iam-service/src/v1/users/users_dto.rs b/imphnen-iam/src/v1/users/users_dto.rs similarity index 83% rename from imphnen-iam-service/src/v1/users/users_dto.rs rename to imphnen-iam/src/v1/users/users_dto.rs index e752f26..2c71866 100644 --- a/imphnen-iam-service/src/v1/users/users_dto.rs +++ b/imphnen-iam/src/v1/users/users_dto.rs @@ -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, - pub phone_number: String, - pub is_active: bool, - pub is_deleted: bool, - pub gender: Option, - pub birthdate: Option, - 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, pub birthdate: Option, + 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, + pub role: RolesDetailQueryDto, pub fullname: String, pub email: String, pub avatar: Option, @@ -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, pub birthdate: Option, - pub role: RolesItemDtoRaw, pub password: String, + pub role: RolesDetailQueryDto, pub created_at: String, pub updated_at: String, } diff --git a/imphnen-iam/src/v1/users/users_repository.rs b/imphnen-iam/src/v1/users/users_repository.rs new file mode 100644 index 0000000..68e87ad --- /dev/null +++ b/imphnen-iam/src/v1/users/users_repository.rs @@ -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>> { + let db = &self.state.surrealdb_ws; + + let raw_result = query_list_with_meta::( + 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::>(); + + Ok(ResponseListSuccessDto { + data, + meta: raw_result.meta, + }) + } + + pub async fn query_user_by_email( + &self, + email: String, + ) -> Result { + let db = &self.state.surrealdb_ws; + + let sql = build_user_by_field_query("email"); + + let user_opt: Option = 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 { + let db = &self.state.surrealdb_ws; + + let sql = build_user_by_field_query(&make_thing("app_users", &id).to_raw()); + + let user_opt: Option = 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 { + let db = &self.state.surrealdb_ws; + let record: Option = 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 { + 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 = 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 { + 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 = 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 { + let db = &self.state.surrealdb_ws; + let record: Option = 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 { + let db = &self.state.surrealdb_ws; + let user = self.query_user_by_email(email).await?; + let record: Option = 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 { + 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 = 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"), + } + } +} diff --git a/imphnen-iam/src/v1/users/users_schema.rs b/imphnen-iam/src/v1/users/users_schema.rs new file mode 100644 index 0000000..10b09aa --- /dev/null +++ b/imphnen-iam/src/v1/users/users_schema.rs @@ -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, + pub phone_number: String, + pub is_active: bool, + pub is_deleted: bool, + pub gender: Option, + pub birthdate: Option, + pub role: Thing, + pub created_at: String, + pub updated_at: String, +} + +impl Crud 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 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) + } +} diff --git a/imphnen-iam-service/src/v1/users/users_service.rs b/imphnen-iam/src/v1/users/users_service.rs similarity index 100% rename from imphnen-iam-service/src/v1/users/users_service.rs rename to imphnen-iam/src/v1/users/users_service.rs diff --git a/imphnen-iam-service/src/v2/.gitkeep b/imphnen-iam/src/v2/.gitkeep similarity index 100% rename from imphnen-iam-service/src/v2/.gitkeep rename to imphnen-iam/src/v2/.gitkeep diff --git a/imphnen-libs/Cargo.toml b/imphnen-libs/Cargo.toml index da99949..6ff4cf7 100644 --- a/imphnen-libs/Cargo.toml +++ b/imphnen-libs/Cargo.toml @@ -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"] } \ No newline at end of file +chrono.workspace = true +surrealdb.workspace = true +jsonwebtoken.workspace = true diff --git a/imphnen-middleware-service/Cargo.toml b/imphnen-middleware/Cargo.toml similarity index 81% rename from imphnen-middleware-service/Cargo.toml rename to imphnen-middleware/Cargo.toml index 4463224..cafff4f 100644 --- a/imphnen-middleware-service/Cargo.toml +++ b/imphnen-middleware/Cargo.toml @@ -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" diff --git a/imphnen-middleware-service/src/auth_middleware/mod.rs b/imphnen-middleware/src/auth_middleware/mod.rs similarity index 94% rename from imphnen-middleware-service/src/auth_middleware/mod.rs rename to imphnen-middleware/src/auth_middleware/mod.rs index 6c3f625..d91068b 100644 --- a/imphnen-middleware-service/src/auth_middleware/mod.rs +++ b/imphnen-middleware/src/auth_middleware/mod.rs @@ -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; diff --git a/imphnen-middleware-service/src/cors_middleware/mod.rs b/imphnen-middleware/src/cors_middleware/mod.rs similarity index 100% rename from imphnen-middleware-service/src/cors_middleware/mod.rs rename to imphnen-middleware/src/cors_middleware/mod.rs diff --git a/imphnen-middleware-service/src/lib.rs b/imphnen-middleware/src/lib.rs similarity index 100% rename from imphnen-middleware-service/src/lib.rs rename to imphnen-middleware/src/lib.rs diff --git a/imphnen-middleware-service/src/payment_middleware/.gitkeep b/imphnen-middleware/src/payment_middleware/.gitkeep similarity index 100% rename from imphnen-middleware-service/src/payment_middleware/.gitkeep rename to imphnen-middleware/src/payment_middleware/.gitkeep diff --git a/imphnen-middleware-service/src/permissions_middleware/mod.rs b/imphnen-middleware/src/permissions_middleware/mod.rs similarity index 97% rename from imphnen-middleware-service/src/permissions_middleware/mod.rs rename to imphnen-middleware/src/permissions_middleware/mod.rs index 27d7641..066c756 100644 --- a/imphnen-middleware-service/src/permissions_middleware/mod.rs +++ b/imphnen-middleware/src/permissions_middleware/mod.rs @@ -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}; diff --git a/imphnen-utils/Cargo.toml b/imphnen-utils/Cargo.toml index 2296459..4823820 100644 --- a/imphnen-utils/Cargo.toml +++ b/imphnen-utils/Cargo.toml @@ -14,4 +14,4 @@ anyhow.workspace = true axum-test.workspace = true serde.workspace = true serde_json.workspace = true -validator.workspace = true \ No newline at end of file +validator.workspace = true diff --git a/imphnen-utils/src/crud.rs b/imphnen-utils/src/crud.rs new file mode 100644 index 0000000..d95669b --- /dev/null +++ b/imphnen-utils/src/crud.rs @@ -0,0 +1,8 @@ +pub trait Crud { + 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") + } +} diff --git a/imphnen-utils/src/lib.rs b/imphnen-utils/src/lib.rs index e21a316..08ddcb6 100644 --- a/imphnen-utils/src/lib.rs +++ b/imphnen-utils/src/lib.rs @@ -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::*; diff --git a/imphnen-utils/src/query_builder.rs b/imphnen-utils/src/query_builder.rs new file mode 100644 index 0000000..33e1a3e --- /dev/null +++ b/imphnen-utils/src/query_builder.rs @@ -0,0 +1,137 @@ +use imphnen_libs::MetaRequestDto; + +pub struct ListQueryBuilder { + resource: String, + conditions: Vec, + limit: usize, + start: usize, + order_by: Option, + order: Option, + fetch: Vec, + select_fields: Vec, +} + +impl ListQueryBuilder { + pub fn from_meta( + resource: impl Into, + meta: &MetaRequestDto, + search_field: impl Into, + select_fields: Option>, + ) -> 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) -> 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, + per_page: Option, + ) -> 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) -> 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 + ) + } +} diff --git a/imphnen-utils/src/query_list.rs b/imphnen-utils/src/query_list.rs index c875694..bb27db8 100644 --- a/imphnen-utils/src/query_list.rs +++ b/imphnen-utils/src/query_list.rs @@ -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( db: &Surreal, @@ -10,75 +10,61 @@ pub async fn query_list_with_meta( meta: &MetaRequestDto, conditions: Vec, custom_select: Option, + search_field: &str, + select_fields: Option>, ) -> Result>> 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 = 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 = 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, + }), }) } diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 8898e4f..f858262 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -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 diff --git a/tests/src/iam_service/auth/auth_controller_test.rs b/tests/src/iam/auth/auth_controller_test.rs similarity index 100% rename from tests/src/iam_service/auth/auth_controller_test.rs rename to tests/src/iam/auth/auth_controller_test.rs diff --git a/tests/src/iam_service/auth/auth_repository_test.rs b/tests/src/iam/auth/auth_repository_test.rs similarity index 100% rename from tests/src/iam_service/auth/auth_repository_test.rs rename to tests/src/iam/auth/auth_repository_test.rs diff --git a/tests/src/iam_service/auth/mod.rs b/tests/src/iam/auth/mod.rs similarity index 100% rename from tests/src/iam_service/auth/mod.rs rename to tests/src/iam/auth/mod.rs diff --git a/tests/src/iam_service/mod.rs b/tests/src/iam/mod.rs similarity index 100% rename from tests/src/iam_service/mod.rs rename to tests/src/iam/mod.rs diff --git a/tests/src/iam_service/permissions/mod.rs b/tests/src/iam/permissions/mod.rs similarity index 100% rename from tests/src/iam_service/permissions/mod.rs rename to tests/src/iam/permissions/mod.rs diff --git a/tests/src/iam_service/permissions/permissions_controller_test.rs b/tests/src/iam/permissions/permissions_controller_test.rs similarity index 100% rename from tests/src/iam_service/permissions/permissions_controller_test.rs rename to tests/src/iam/permissions/permissions_controller_test.rs diff --git a/tests/src/iam_service/permissions/permissions_repository_test.rs b/tests/src/iam/permissions/permissions_repository_test.rs similarity index 100% rename from tests/src/iam_service/permissions/permissions_repository_test.rs rename to tests/src/iam/permissions/permissions_repository_test.rs diff --git a/tests/src/iam_service/roles/mod.rs b/tests/src/iam/roles/mod.rs similarity index 100% rename from tests/src/iam_service/roles/mod.rs rename to tests/src/iam/roles/mod.rs diff --git a/tests/src/iam_service/roles/roles_controller_test.rs b/tests/src/iam/roles/roles_controller_test.rs similarity index 100% rename from tests/src/iam_service/roles/roles_controller_test.rs rename to tests/src/iam/roles/roles_controller_test.rs diff --git a/tests/src/iam_service/roles/roles_repository_test.rs b/tests/src/iam/roles/roles_repository_test.rs similarity index 100% rename from tests/src/iam_service/roles/roles_repository_test.rs rename to tests/src/iam/roles/roles_repository_test.rs diff --git a/tests/src/iam_service/users/mod.rs b/tests/src/iam/users/mod.rs similarity index 100% rename from tests/src/iam_service/users/mod.rs rename to tests/src/iam/users/mod.rs diff --git a/tests/src/iam_service/users/users_controller_test.rs b/tests/src/iam/users/users_controller_test.rs similarity index 100% rename from tests/src/iam_service/users/users_controller_test.rs rename to tests/src/iam/users/users_controller_test.rs diff --git a/tests/src/iam_service/users/users_repository_test.rs b/tests/src/iam/users/users_repository_test.rs similarity index 100% rename from tests/src/iam_service/users/users_repository_test.rs rename to tests/src/iam/users/users_repository_test.rs diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 0c46bb0..eded98f 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -1,6 +1,6 @@ pub use imphnen_entities::*; -pub use imphnen_iam_service::*; +pub use imphnen_iam::*; pub use imphnen_utils::*; #[cfg(test)] -pub mod iam_service; +pub mod iam; From e48507901e1e912b5a467b0f6eb33baf933beb78 Mon Sep 17 00:00:00 2001 From: Maulana Sodiqin Date: Sun, 18 May 2025 03:12:20 +0700 Subject: [PATCH 2/4] chore: normalize code #2 --- .../src/v1/permissions/permissions_dto.rs | 11 ++ .../src/v1/permissions/permissions_enum.rs | 2 + imphnen-iam/src/v1/roles/roles_controller.rs | 15 +- imphnen-iam/src/v1/roles/roles_dto.rs | 32 +++- imphnen-iam/src/v1/roles/roles_enum.rs | 6 +- imphnen-iam/src/v1/roles/roles_repository.rs | 180 ++++++++---------- imphnen-iam/src/v1/roles/roles_schema.rs | 88 ++++++++- imphnen-iam/src/v1/roles/roles_service.rs | 2 +- imphnen-iam/src/v1/users/users_controller.rs | 16 +- imphnen-iam/src/v1/users/users_dto.rs | 35 +++- imphnen-iam/src/v1/users/users_repository.rs | 172 ++++++++--------- imphnen-iam/src/v1/users/users_schema.rs | 119 ++++++++---- imphnen-iam/src/v1/users/users_service.rs | 180 ++++++++---------- imphnen-utils/src/make_thing.rs | 4 + imphnen-utils/src/query_builder.rs | 106 +++++++++++ 15 files changed, 603 insertions(+), 365 deletions(-) diff --git a/imphnen-iam/src/v1/permissions/permissions_dto.rs b/imphnen-iam/src/v1/permissions/permissions_dto.rs index c589e21..19aaf3b 100644 --- a/imphnen-iam/src/v1/permissions/permissions_dto.rs +++ b/imphnen-iam/src/v1/permissions/permissions_dto.rs @@ -17,6 +17,17 @@ pub struct PermissionsItemDto { pub updated_at: Option, } +impl PermissionsItemDto { + pub fn from(dto: &PermissionsQueryDto) -> Self { + Self { + id: dto.id.id.to_raw(), + name: dto.name.clone(), + created_at: dto.created_at.clone(), + updated_at: dto.updated_at.clone(), + } + } +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PermissionsQueryDto { pub id: Thing, diff --git a/imphnen-iam/src/v1/permissions/permissions_enum.rs b/imphnen-iam/src/v1/permissions/permissions_enum.rs index f544e0e..aa244fe 100644 --- a/imphnen-iam/src/v1/permissions/permissions_enum.rs +++ b/imphnen-iam/src/v1/permissions/permissions_enum.rs @@ -7,6 +7,7 @@ pub enum PermissionsEnum { CreateUsers, DeleteUsers, UpdateUsers, + ActivateUsers, ReadListRoles, ReadDetailRoles, CreateRoles, @@ -27,6 +28,7 @@ impl fmt::Display for PermissionsEnum { PermissionsEnum::CreateUsers => "Create Users", PermissionsEnum::DeleteUsers => "Delete Users", PermissionsEnum::UpdateUsers => "Update Users", + PermissionsEnum::ActivateUsers => "Activate Users", PermissionsEnum::ReadListRoles => "Read List Roles", PermissionsEnum::ReadDetailRoles => "Read Detail Roles", PermissionsEnum::CreateRoles => "Create Roles", diff --git a/imphnen-iam/src/v1/roles/roles_controller.rs b/imphnen-iam/src/v1/roles/roles_controller.rs index ecb8151..e9333eb 100644 --- a/imphnen-iam/src/v1/roles/roles_controller.rs +++ b/imphnen-iam/src/v1/roles/roles_controller.rs @@ -1,13 +1,16 @@ use axum::{ + Extension, Json, extract::{Path, Query}, response::IntoResponse, - Extension, Json, }; -use super::{RolesItemDto, RolesRequestCreateDto, RolesRequestUpdateDto}; +use super::{ + RolesDetailItemDto, RolesListItemDto, RolesRequestCreateDto, RolesRequestUpdateDto, +}; use crate::{ - permissions_guard, v1::roles_service::RolesService, AppState, MessageResponseDto, - MetaRequestDto, PermissionsEnum, ResponseListSuccessDto, ResponseSuccessDto, + AppState, MessageResponseDto, MetaRequestDto, PermissionsEnum, + ResponseListSuccessDto, ResponseSuccessDto, permissions_guard, + v1::roles_service::RolesService, }; #[utoipa::path( @@ -26,7 +29,7 @@ use crate::{ ("filter_by" = Option, Query, description = "Field to filter by"), ), responses( - (status = 200, description = "Get role list", body = ResponseListSuccessDto>) + (status = 200, description = "Get role list", body = ResponseListSuccessDto>) ), tag = "Roles" )] @@ -55,7 +58,7 @@ pub async fn get_role_list( path = "/v1/roles/detail/{id}", params(("id" = String, Path, description = "Role ID")), responses( - (status = 200, description = "Get role by ID", body = ResponseSuccessDto) + (status = 200, description = "Get role by ID", body = ResponseSuccessDto) ), tag = "Roles" )] diff --git a/imphnen-iam/src/v1/roles/roles_dto.rs b/imphnen-iam/src/v1/roles/roles_dto.rs index 28dc98a..6cb541e 100644 --- a/imphnen-iam/src/v1/roles/roles_dto.rs +++ b/imphnen-iam/src/v1/roles/roles_dto.rs @@ -5,17 +5,25 @@ use utoipa::ToSchema; use validator::Validate; #[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)] -pub struct RolesRequestDto { +pub struct RolesRequestUpdateDto { + #[validate(length(min = 1, message = "Role name must not be empty"))] + pub name: Option, + pub permissions: Option>, + pub overwrite: Option, +} + +#[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: Option>, + pub permissions: Vec, } #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] pub struct RolesListItemDto { pub id: String, pub name: String, - pub permissions_count: u64, + pub permissions_count: usize, pub created_at: Option, pub updated_at: Option, } @@ -24,11 +32,29 @@ pub struct RolesListItemDto { pub struct RolesDetailItemDto { pub id: String, pub name: String, + pub is_deleted: bool, pub permissions: Vec, pub created_at: Option, pub updated_at: Option, } +impl RolesDetailItemDto { + pub fn from(dto: &RolesDetailQueryDto) -> Self { + Self { + id: dto.id.id.to_raw(), + name: dto.name.clone(), + is_deleted: dto.is_deleted, + permissions: dto + .permissions + .iter() + .map(PermissionsItemDto::from) + .collect(), + created_at: dto.created_at.clone(), + updated_at: dto.updated_at.clone(), + } + } +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct RolesDetailQueryDto { pub id: Thing, diff --git a/imphnen-iam/src/v1/roles/roles_enum.rs b/imphnen-iam/src/v1/roles/roles_enum.rs index d377243..202c22f 100644 --- a/imphnen-iam/src/v1/roles/roles_enum.rs +++ b/imphnen-iam/src/v1/roles/roles_enum.rs @@ -4,8 +4,7 @@ use std::fmt; pub enum RolesEnum { Admin, User, - Student, - Staf, + Staff, } impl fmt::Display for RolesEnum { @@ -13,8 +12,7 @@ impl fmt::Display for RolesEnum { let roles_str = match self { RolesEnum::Admin => "Admin", RolesEnum::User => "User", - RolesEnum::Student => "Student", - RolesEnum::Staf => "Staf", + RolesEnum::Staff => "Staff", }; write!(f, "{}", roles_str) } diff --git a/imphnen-iam/src/v1/roles/roles_repository.rs b/imphnen-iam/src/v1/roles/roles_repository.rs index 46ebc50..fcae8da 100644 --- a/imphnen-iam/src/v1/roles/roles_repository.rs +++ b/imphnen-iam/src/v1/roles/roles_repository.rs @@ -1,13 +1,15 @@ use super::{ - RolesItemByIdDto, RolesItemByIdDtoRaw, RolesItemDto, RolesItemDtoRaw, RolesRequestCreateDto, RolesRequestUpdateDto, RolesSchema + RolesDetailItemDto, RolesDetailQueryDto, RolesListItemDto, RolesRequestCreateDto, + RolesRequestUpdateDto, RolesSchema, }; use crate::{ - extract_id, get_id, make_thing, query_list_with_meta, AppState, MetaRequestDto, - PermissionsItemDto, ResourceEnum, ResponseListSuccessDto, + AppState, MetaRequestDto, PermissionsItemDto, ResourceEnum, + ResponseListSuccessDto, extract_id, get_id, make_thing, query_list_with_meta, }; -use anyhow::{bail, Result}; -use surrealdb::sql::Thing; +use anyhow::{Result, bail}; +use imphnen_utils::DetailQueryBuilder; use surrealdb::Uuid; +use surrealdb::sql::Thing; pub struct RolesRepository<'a> { state: &'a AppState, @@ -18,74 +20,73 @@ impl<'a> RolesRepository<'a> { Self { state } } - pub async fn query_raw_role_by_id(&self, id: &str) -> Result { - let db = &self.state.surrealdb_ws; - let role: Option = - db.select((ResourceEnum::Roles.to_string(), id)).await?; - match role { - Some(r) if !r.is_deleted => Ok(r), - _ => bail!("Role not found"), - } - } - pub async fn query_role_list( &self, meta: MetaRequestDto, - ) -> Result>> { + ) -> Result>> { let mut conditions = vec!["is_deleted = false".into()]; - if meta.search.is_some() { - conditions.push("string::contains(name, $search)".into()); + if let Some(_search) = meta.search.as_deref().filter(|s| !s.is_empty()) { + conditions.push("string::contains(name ?? '', $search)".into()); } - if meta.filter_by.is_some() && meta.filter.is_some() { - let filter_by = meta.filter_by.as_ref().unwrap(); - conditions.push(format!("{} = $filter", filter_by)); + if let (Some(filter_by), Some(filter_val)) = + (meta.filter_by.as_ref(), meta.filter.as_ref()) + { + if !filter_val.is_empty() { + conditions.push(format!("{} = $filter", filter_by)); + } } - let raw_result: ResponseListSuccessDto> = query_list_with_meta( - &self.state.surrealdb_ws, - &ResourceEnum::Roles.to_string(), - &meta, - conditions, - None, - ) - .await?; - let transformed_data = raw_result + let raw_result: ResponseListSuccessDto> = + query_list_with_meta( + &self.state.surrealdb_ws, + &ResourceEnum::Roles.to_string(), + &meta, + conditions, + None, + "name", + None, + ) + .await?; + let data = raw_result .data .into_iter() - .map(|role| { - RolesItemDto { - name: role.name, - created_at: role.created_at, - updated_at: role.updated_at, - permissions: 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::>(), - id: extract_id(&role.id), - - } + .map(|role| RolesListItemDto { + id: extract_id(&role.id), + name: role.name, + created_at: role.created_at, + updated_at: role.updated_at, + permissions_count: role.permissions.len(), }) - .collect::>(); - let transformed_meta = raw_result.meta; + .collect(); Ok(ResponseListSuccessDto { - data: transformed_data, - meta: transformed_meta, + data, + meta: raw_result.meta, }) } - pub async fn query_role_by_name(&self, name: String) -> Result { + pub async fn query_role_by_name( + &self, + name: String, + ) -> Result { let db = &self.state.surrealdb_ws; - let sql = format!( - "SELECT *, permissions FROM {} WHERE name = $name AND is_deleted = false LIMIT 1 FETCH permissions", - ResourceEnum::Roles.to_string() - ); - let mut result = db.query(sql).bind(("name", name.clone())).await?; - let role: Option = result.take(0)?; - let role = match role { + let builder = DetailQueryBuilder::new(ResourceEnum::Roles.to_string()) + .with_where("name") + .where_value(name.clone()) + .with_select_fields(vec![ + "id", + "name", + "permissions", + "created_at", + "updated_at", + "is_deleted", + ]) + .with_fetch("permissions"); + + let sql = builder.build(); + let result: Option = builder + .apply_bindings(db.query(sql).bind(("name", name))) + .await? + .take(0)?; + let role = match result { Some(r) if !r.is_deleted => r, _ => bail!("Role not found"), }; @@ -98,8 +99,8 @@ impl<'a> RolesRepository<'a> { created_at: perm.created_at, updated_at: perm.updated_at, }) - .collect::>(); - Ok(RolesItemByIdDto { + .collect(); + Ok(RolesDetailItemDto { id: extract_id(&role.id), name: role.name, is_deleted: role.is_deleted, @@ -109,16 +110,23 @@ impl<'a> RolesRepository<'a> { }) } - pub async fn query_role_by_id(&self, id: String) -> Result { + pub async fn query_role_by_id(&self, id: String) -> Result { let db = &self.state.surrealdb_ws; - let query = format!( - "SELECT *, permissions.* AS permissions - FROM app_roles:⟨{}⟩ WHERE is_deleted = false FETCH permissions", - id - ); - let mut result = db.query(query).await?; - let role: Option = result.take(0)?; - let role = match role { + let builder = DetailQueryBuilder::new(ResourceEnum::Roles.to_string()) + .with_id(&id) + .with_select_fields(vec![ + "id", + "name", + "is_deleted", + "permissions", + "created_at", + "updated_at", + ]) + .with_fetch("permissions"); + let sql = builder.build(); + let result: Option = + builder.apply_bindings(db.query(sql)).await?.take(0)?; + let role = match result { Some(r) if !r.is_deleted => r, _ => bail!("Role not found"), }; @@ -131,8 +139,8 @@ impl<'a> RolesRepository<'a> { created_at: perm.created_at, updated_at: perm.updated_at, }) - .collect::>(); - Ok(RolesItemByIdDto { + .collect(); + Ok(RolesDetailItemDto { id: extract_id(&role.id), name: role.name, is_deleted: role.is_deleted, @@ -147,14 +155,12 @@ impl<'a> RolesRepository<'a> { payload: RolesRequestCreateDto, ) -> Result { let db = &self.state.surrealdb_ws; - let role_id = Uuid::new_v4().to_string(); let permission_things: Vec = payload .permissions .iter() .map(|id| make_thing(&ResourceEnum::Permissions.to_string(), id)) .collect(); - let role = RolesSchema { id: make_thing(&ResourceEnum::Roles.to_string(), &role_id), name: payload.name, @@ -163,12 +169,10 @@ impl<'a> RolesRepository<'a> { created_at: Some(crate::get_iso_date()), updated_at: Some(crate::get_iso_date()), }; - let _: Option = db .create((&ResourceEnum::Roles.to_string(), role_id)) .content(role) .await?; - Ok("Role with permissions created successfully".into()) } @@ -178,31 +182,11 @@ impl<'a> RolesRepository<'a> { data: RolesRequestUpdateDto, ) -> Result { let db = &self.state.surrealdb_ws; - let thing_id = make_thing(&ResourceEnum::Roles.to_string(), &id); - let existing = self.query_raw_role_by_id(&id).await?; + let existing = self.query_role_by_id(id.clone()).await?; if existing.is_deleted { bail!("Role already deleted"); } - let permissions: Vec = if let Some(permission_ids) = &data.permissions { - permission_ids - .iter() - .map(|id| make_thing(&ResourceEnum::Permissions.to_string(), id)) - .collect() - } else { - existing - .permissions - .iter() - .map(|p| make_thing(&ResourceEnum::Permissions.to_string(), &p.id.to_raw())) - .collect() - }; - let merged = RolesSchema { - id: thing_id, - name: data.name.unwrap_or(existing.name), - permissions, - is_deleted: existing.is_deleted, - created_at: existing.created_at, - updated_at: Some(crate::get_iso_date()), - }; + let merged = RolesSchema::update(data, id.clone(), existing); let record: Option = db.update(get_id(&merged.id)?).content(merged).await?; match record { diff --git a/imphnen-iam/src/v1/roles/roles_schema.rs b/imphnen-iam/src/v1/roles/roles_schema.rs index d5ae49d..4898257 100644 --- a/imphnen-iam/src/v1/roles/roles_schema.rs +++ b/imphnen-iam/src/v1/roles/roles_schema.rs @@ -1,7 +1,12 @@ +use super::{ + RolesDetailItemDto, RolesDetailQueryDto, RolesRequestCreateDto, + RolesRequestUpdateDto, +}; +use crate::{ResourceEnum, make_thing}; +use imphnen_utils::get_iso_date; use serde::{Deserialize, Serialize}; -use surrealdb::{sql::Thing, Uuid}; - -use crate::{make_thing, ResourceEnum}; +use std::collections::HashSet; +use surrealdb::{Uuid, sql::Thing}; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct RolesSchema { @@ -31,3 +36,80 @@ impl Default for RolesSchema { } } } + +impl RolesSchema { + pub fn from(dto: RolesDetailQueryDto) -> Self { + Self { + id: dto.id, + name: dto.name, + permissions: dto + .permissions + .into_iter() + .map(|perm| { + make_thing(&ResourceEnum::Permissions.to_string(), &perm.id.to_raw()) + }) + .collect(), + is_deleted: dto.is_deleted, + created_at: dto.created_at, + updated_at: dto.updated_at, + } + } + + pub fn create(dto: RolesRequestCreateDto) -> Self { + let permissions: Vec = dto + .permissions + .into_iter() + .map(|id| make_thing(&ResourceEnum::Permissions.to_string(), &id)) + .collect(); + Self { + id: make_thing( + &ResourceEnum::Roles.to_string(), + &Uuid::new_v4().to_string(), + ), + name: dto.name, + permissions, + is_deleted: false, + created_at: Some(get_iso_date()), + updated_at: Some(get_iso_date()), + } + } + + pub fn update( + dto: RolesRequestUpdateDto, + id: String, + existing: RolesDetailItemDto, + ) -> Self { + let name = dto.name.unwrap_or(existing.name); + let permissions: Vec = + match (dto.permissions, dto.overwrite.unwrap_or(false)) { + (Some(new_ids), true) => new_ids + .iter() + .map(|id| make_thing(&ResourceEnum::Permissions.to_string(), id)) + .collect(), + (Some(new_ids), false) => { + let mut all_ids: HashSet = + existing.permissions.iter().map(|p| p.id.clone()).collect(); + for id in new_ids { + all_ids.insert(id); + } + all_ids + .into_iter() + .map(|id| make_thing(&ResourceEnum::Permissions.to_string(), &id)) + .collect() + } + (None, _) => existing + .permissions + .iter() + .map(|p| make_thing(&ResourceEnum::Permissions.to_string(), &p.id)) + .collect(), + }; + Self { + id: make_thing(&ResourceEnum::Roles.to_string(), &id), + name, + permissions, + is_deleted: existing.is_deleted, + created_at: existing.created_at, + updated_at: Some(get_iso_date()), + } + } +} diff --git a/imphnen-iam/src/v1/roles/roles_service.rs b/imphnen-iam/src/v1/roles/roles_service.rs index f381d7a..b21df84 100644 --- a/imphnen-iam/src/v1/roles/roles_service.rs +++ b/imphnen-iam/src/v1/roles/roles_service.rs @@ -1,7 +1,7 @@ use super::{RolesRepository, RolesRequestCreateDto, RolesRequestUpdateDto}; use crate::{ - common_response, success_list_response, success_response, validate_request, AppState, MetaRequestDto, ResponseListSuccessDto, ResponseSuccessDto, + common_response, success_list_response, success_response, validate_request, }; use axum::{http::StatusCode, response::Response}; diff --git a/imphnen-iam/src/v1/users/users_controller.rs b/imphnen-iam/src/v1/users/users_controller.rs index 51784ea..442a78c 100644 --- a/imphnen-iam/src/v1/users/users_controller.rs +++ b/imphnen-iam/src/v1/users/users_controller.rs @@ -1,16 +1,16 @@ +use crate::{AppState, MetaRequestDto, v1::users_service::UsersService}; +use crate::{ + MessageResponseDto, PermissionsEnum, ResponseListSuccessDto, ResponseSuccessDto, + UsersCreateRequestDto, UsersDetailItemDto, permissions_guard, +}; use axum::extract::{Path, Query}; use axum::http::HeaderMap; use axum::response::IntoResponse; use axum::{Extension, Json}; -use crate::{ - permissions_guard, MessageResponseDto, PermissionsEnum, ResponseListSuccessDto, - ResponseSuccessDto, UsersActiveInactiveRequestDto, UsersCreateRequestDto, - UsersDetailItemDto, +use super::{ + UsersActiveInactiveRequestDto, UsersListItemDto, UsersUpdateRequestDto, }; -use crate::{v1::users_service::UsersService, AppState, MetaRequestDto}; - -use super::{UsersListItemDto, UsersUpdateRequestDto}; #[utoipa::path( get, @@ -204,7 +204,7 @@ pub async fn patch_user_active_status( match permissions_guard( &headers, state.clone(), - vec![PermissionsEnum::UpdateUsers], + vec![PermissionsEnum::ActivateUsers], ) .await { diff --git a/imphnen-iam/src/v1/users/users_dto.rs b/imphnen-iam/src/v1/users/users_dto.rs index 2c71866..41c29f6 100644 --- a/imphnen-iam/src/v1/users/users_dto.rs +++ b/imphnen-iam/src/v1/users/users_dto.rs @@ -1,4 +1,4 @@ -use crate::{RolesDetailQueryDto, RolesItemDto}; +use crate::{RolesDetailItemDto, RolesDetailQueryDto}; use lazy_static::lazy_static; use regex::Regex; use serde::{Deserialize, Serialize}; @@ -10,6 +10,17 @@ 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)] +pub struct UsersSetNewPasswordRequestDto { + pub password: String, + pub old_password: String, +} + #[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)] pub struct UsersCreateRequestDto { #[validate( @@ -71,7 +82,7 @@ pub struct UsersUpdateRequestDto { #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] pub struct UsersDetailItemDto { pub id: String, - pub role: RolesItemDto, + pub role: RolesDetailItemDto, pub fullname: String, pub email: String, pub avatar: Option, @@ -83,6 +94,24 @@ pub struct UsersDetailItemDto { pub updated_at: String, } +impl UsersDetailItemDto { + pub fn from(dto: UsersDetailQueryDto) -> Self { + Self { + id: dto.id.id.to_raw(), + role: RolesDetailItemDto::from(&dto.role), + fullname: dto.fullname, + email: dto.email, + avatar: dto.avatar, + phone_number: dto.phone_number, + is_active: dto.is_active, + gender: dto.gender, + birthdate: dto.birthdate, + created_at: dto.created_at, + updated_at: dto.updated_at, + } + } +} + #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] pub struct UsersListItemDto { pub id: String, @@ -110,7 +139,7 @@ pub struct UsersListQueryDto { } impl UsersListQueryDto { - pub fn list_from(&self, role: String) -> UsersListItemDto { + pub fn from(&self, role: String) -> UsersListItemDto { UsersListItemDto { id: self.id.id.to_raw(), role, diff --git a/imphnen-iam/src/v1/users/users_repository.rs b/imphnen-iam/src/v1/users/users_repository.rs index 68e87ad..feab5df 100644 --- a/imphnen-iam/src/v1/users/users_repository.rs +++ b/imphnen-iam/src/v1/users/users_repository.rs @@ -1,27 +1,30 @@ use super::{UsersDetailQueryDto, UsersListItemDto, UsersListQueryDto, UsersSchema}; use crate::{ - AppState, MetaRequestDto, PermissionsItemDto, PermissionsItemDtoRaw, ResourceEnum, - ResponseListSuccessDto, RolesDetailQueryDto, extract_id, get_id, make_thing, + AppState, MetaRequestDto, PermissionsQueryDto, ResourceEnum, + ResponseListSuccessDto, RolesDetailQueryDto, get_id, make_thing, query_list_with_meta, }; use anyhow::{Result, bail}; +use imphnen_utils::DetailQueryBuilder; +use surrealdb::{Surreal, engine::remote::ws::Client}; 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 - ) +pub async fn update_partial_schema( + db: &Surreal, + table: &str, + id: &str, + patch: UsersSchema, +) -> Result { + let thing = make_thing(table, id); + let record_key = get_id(&thing)?; + let result: Option = db.update(record_key).merge(patch).await?; + match result { + Some(_) => Ok("Success update".into()), + None => bail!("Failed to update"), + } } impl<'a> UsersRepository<'a> { @@ -59,7 +62,7 @@ impl<'a> UsersRepository<'a> { .into_iter() .map(|schema| { let role = schema.clone().role.name; - schema.list_from(role) + schema.from(role) }) .collect::>(); @@ -75,13 +78,31 @@ impl<'a> UsersRepository<'a> { ) -> Result { let db = &self.state.surrealdb_ws; - let sql = build_user_by_field_query("email"); + let builder = DetailQueryBuilder::new(ResourceEnum::Users.to_string()) + .with_where("email") + .where_value(email.clone()) + .with_select_fields(vec![ + "id", + "fullname", + "email", + "avatar", + "phone_number", + "is_active", + "is_deleted", + "gender", + "birthdate", + "password", + "created_at", + "updated_at", + "role", + ]) + .with_fetch("role") + .with_fetch("role.permissions"); - let user_opt: Option = db - .query(sql) - .bind(("email", email.clone())) - .await? - .take(0)?; + let sql = builder.build(); + + let user_opt: Option = + builder.apply_bindings(db.query(sql)).await?.take(0)?; let Some(user) = user_opt else { bail!("User not found"); @@ -95,7 +116,7 @@ impl<'a> UsersRepository<'a> { .role .permissions .into_iter() - .map(|perm| PermissionsItemDtoRaw { + .map(|perm| PermissionsQueryDto { id: perm.id, name: perm.name, created_at: perm.created_at, @@ -130,15 +151,32 @@ impl<'a> UsersRepository<'a> { pub async fn query_user_by_id(&self, id: String) -> Result { let db = &self.state.surrealdb_ws; - let sql = build_user_by_field_query(&make_thing("app_users", &id).to_raw()); + let builder = DetailQueryBuilder::new(ResourceEnum::Users.to_string()) + .with_id(&id) + .with_select_fields(vec![ + "id", + "fullname", + "email", + "avatar", + "phone_number", + "is_active", + "is_deleted", + "gender", + "birthdate", + "password", + "created_at", + "updated_at", + "role", + ]) + .with_fetch("role") + .with_fetch("role.permissions"); - let user_opt: Option = db - .query(sql) - .bind(("email", make_thing("app_users", &id).to_raw())) - .await? - .take(0)?; + let sql = builder.build(); - let Some(user) = user_opt else { + let result: Option = + builder.apply_bindings(db.query(sql)).await?.take(0)?; + + let Some(user) = result else { bail!("User not found"); }; @@ -150,7 +188,7 @@ impl<'a> UsersRepository<'a> { .role .permissions .into_iter() - .map(|perm| PermissionsItemDtoRaw { + .map(|perm| PermissionsQueryDto { id: perm.id, name: perm.name, created_at: perm.created_at, @@ -201,10 +239,15 @@ impl<'a> UsersRepository<'a> { if existing.is_deleted { bail!("User already deleted"); } + let role_thing = if data.role == existing.role.id { + existing.role.id + } else { + data.clone().role + }; let merged = UsersSchema { password: existing.password, created_at: existing.created_at, - role: make_thing("app_roles", &existing.role.id), + role: role_thing, ..data.clone() }; let record: Option = db.update(record_key).merge(merged).await?; @@ -214,76 +257,13 @@ impl<'a> UsersRepository<'a> { } } - pub async fn query_active_inactive_user( - &self, - email: String, - data: UsersActiveInactiveSchema, - ) -> Result { + pub async fn query_delete_user(&self, id: String) -> Result { let db = &self.state.surrealdb_ws; - let user = self.query_user_by_email(email.clone()).await?; + let user = self.query_user_by_id(id).await?; if user.is_deleted { bail!("User already deleted"); } let record_key = get_id(&user.id)?; - let record: Option = 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 { - let db = &self.state.surrealdb_ws; - let record: Option = 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 { - let db = &self.state.surrealdb_ws; - let user = self.query_user_by_email(email).await?; - let record: Option = 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 { - 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 = db .update(record_key) .merge(serde_json::json!({ "is_deleted": true })) diff --git a/imphnen-iam/src/v1/users/users_schema.rs b/imphnen-iam/src/v1/users/users_schema.rs index 10b09aa..b80cd2e 100644 --- a/imphnen-iam/src/v1/users/users_schema.rs +++ b/imphnen-iam/src/v1/users/users_schema.rs @@ -1,8 +1,8 @@ -use super::{UsersDetailItemDto, UsersListItemDto}; -use crate::RolesItemDto; -use imphnen_utils::Crud; +use super::{UsersCreateRequestDto, UsersDetailQueryDto, UsersUpdateRequestDto}; +use imphnen_libs::{ResourceEnum, hash_password}; +use imphnen_utils::{get_iso_date, make_thing}; use serde::{Deserialize, Serialize}; -use surrealdb::sql::Thing; +use surrealdb::{Uuid, sql::Thing}; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct UsersSchema { @@ -21,46 +21,89 @@ pub struct UsersSchema { pub updated_at: String, } -impl Crud 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 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 Default for UsersSchema { + fn default() -> Self { + Self { + id: Thing::from(("app_users", "dummy")), + fullname: "".into(), + email: "".into(), + password: "".into(), + avatar: None, + phone_number: "".into(), + is_active: false, + is_deleted: false, + gender: None, + birthdate: None, + role: Thing::from(("app_roles", "dummy")), + created_at: "".into(), + updated_at: "".into(), } } } impl UsersSchema { - pub fn list_from(&self, role: String) -> UsersListItemDto { - self.list(role) + pub fn from(dto: UsersDetailQueryDto) -> Self { + Self { + id: dto.id, + fullname: dto.fullname, + email: dto.email, + avatar: dto.avatar, + phone_number: dto.phone_number, + is_active: dto.is_active, + is_deleted: dto.is_deleted, + gender: dto.gender, + birthdate: dto.birthdate, + password: dto.password, + created_at: dto.created_at, + updated_at: dto.updated_at, + role: make_thing(&ResourceEnum::Roles.to_string(), &dto.role.id.to_string()), + } } - pub fn detail_from(&self, role: RolesItemDto) -> UsersDetailItemDto { - self.detail(role) + pub fn update(user: UsersUpdateRequestDto, id: String) -> Self { + Self { + id: make_thing(&ResourceEnum::Users.to_string(), &id), + fullname: user.fullname, + email: user.email, + phone_number: user.phone_number, + is_active: user.is_active, + gender: user.gender, + birthdate: user.birthdate, + avatar: user.avatar, + is_deleted: false, + role: make_thing(&ResourceEnum::Roles.to_string(), &user.role_id), + updated_at: get_iso_date(), + ..Default::default() + } + } + + pub fn create(user: UsersCreateRequestDto) -> Self { + let password = hash_password(&user.password).unwrap(); + Self { + id: make_thing( + &ResourceEnum::Users.to_string(), + &Uuid::new_v4().to_string(), + ), + fullname: user.fullname, + email: user.email, + password, + phone_number: user.phone_number, + is_active: false, + gender: None, + birthdate: None, + avatar: None, + is_deleted: false, + role: make_thing(&ResourceEnum::Roles.to_string(), &user.role_id), + created_at: get_iso_date(), + updated_at: get_iso_date(), + } + } + + pub fn patch_password(dto: UsersDetailQueryDto, password: String) -> Self { + Self { + password, + id: dto.id.clone(), + ..Self::from(dto) + } } } diff --git a/imphnen-iam/src/v1/users/users_service.rs b/imphnen-iam/src/v1/users/users_service.rs index 1009a96..f4f41f9 100644 --- a/imphnen-iam/src/v1/users/users_service.rs +++ b/imphnen-iam/src/v1/users/users_service.rs @@ -1,19 +1,17 @@ -use crate::{ - common_response, extract_email, get_iso_date, hash_password, make_thing, - success_list_response, success_response, validate_request, ResourceEnum, - ResponseSuccessDto, +use super::{ + UsersActiveInactiveRequestDto, UsersCreateRequestDto, UsersDetailItemDto, + UsersSetNewPasswordRequestDto, UsersUpdateRequestDto, }; use crate::{ - AppState, MetaRequestDto, ResponseListSuccessDto, UsersActiveInactiveSchema, - UsersRepository, UsersSchema, UsersSetNewPasswordSchema, + AppState, MetaRequestDto, ResponseListSuccessDto, UsersRepository, UsersSchema, +}; +use crate::{ + ResourceEnum, ResponseSuccessDto, common_response, extract_email, make_thing, + success_list_response, success_response, validate_request, }; use axum::http::HeaderMap; use axum::{http::StatusCode, response::Response}; - -use super::{ - UsersActiveInactiveRequestDto, UsersCreateRequestDto, UsersDetailItemDto, - UsersUpdateRequestDto, -}; +use imphnen_libs::{hash_password, verify_password}; pub struct UsersService; @@ -36,17 +34,7 @@ impl UsersService { let repo = UsersRepository::new(state); match repo.query_user_by_id(id).await { Ok(user) if !user.is_deleted => success_response(ResponseSuccessDto { - data: UsersDetailItemDto { - id: user.id, - role: user.role, - fullname: user.fullname, - email: user.email, - avatar: user.avatar, - phone_number: user.phone_number, - is_active: user.is_active, - gender: user.gender, - birthdate: user.birthdate, - }, + data: UsersDetailItemDto::from(user), }), Ok(_) => common_response(StatusCode::NOT_FOUND, "User not found"), Err(e) => common_response(StatusCode::NOT_FOUND, &e.to_string()), @@ -55,22 +43,15 @@ impl UsersService { pub async fn get_user_me(headers: HeaderMap, state: &AppState) -> Response { let repo = UsersRepository::new(state); - let email = extract_email(&headers).unwrap(); - let user = repo.query_user_by_email(email).await.unwrap(); - match repo.query_user_by_id(user.id.id.to_raw()).await { - Ok(user) => success_response(ResponseSuccessDto { - data: UsersDetailItemDto { - id: user.id, - role: user.role, - fullname: user.fullname, - email: user.email, - avatar: user.avatar, - phone_number: user.phone_number, - is_active: user.is_active, - gender: user.gender, - birthdate: user.birthdate, - }, + let email = match extract_email(&headers) { + Some(email) => email, + None => return common_response(StatusCode::UNAUTHORIZED, "Invalid token"), + }; + match repo.query_user_by_email(email).await { + Ok(user) if !user.is_deleted => success_response(ResponseSuccessDto { + data: UsersDetailItemDto::from(user), }), + Ok(_) => common_response(StatusCode::NOT_FOUND, "User not found"), Err(e) => common_response(StatusCode::NOT_FOUND, &e.to_string()), } } @@ -90,19 +71,7 @@ impl UsersService { { return common_response(StatusCode::BAD_REQUEST, "User already exists"); } - let role_thing = make_thing(&ResourceEnum::Roles.to_string(), &new_user.role_id); - match repo - .query_create_user(UsersSchema { - email: new_user.email.clone(), - fullname: new_user.fullname.clone(), - password: hash_password(&new_user.password).unwrap(), - phone_number: new_user.phone_number.clone(), - is_active: new_user.is_active.clone(), - role: role_thing, - ..Default::default() - }) - .await - { + match repo.query_create_user(UsersSchema::create(new_user)).await { Ok(msg) => common_response(StatusCode::CREATED, &msg), Err(err) => { common_response(StatusCode::INTERNAL_SERVER_ERROR, &err.to_string()) @@ -116,28 +85,10 @@ impl UsersService { user: UsersUpdateRequestDto, ) -> Response { let repo = UsersRepository::new(state); - if let Err((status, message)) = validate_request(&user) { return common_response(status, &message); } - - let user_id = make_thing(&ResourceEnum::Users.to_string(), &id); - let role_id = make_thing(&ResourceEnum::Roles.to_string(), ""); - - let updated_user = UsersSchema { - id: user_id, - fullname: user.fullname, - email: user.email, - phone_number: user.phone_number, - is_active: user.is_active, - gender: user.gender, - birthdate: user.birthdate, - avatar: user.avatar, - role: role_id, - updated_at: get_iso_date(), - ..Default::default() - }; - + let updated_user = UsersSchema::update(user, id); match repo.query_update_user(updated_user).await { Ok(msg) => common_response(StatusCode::OK, &msg), Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()), @@ -150,30 +101,18 @@ impl UsersService { user: UsersUpdateRequestDto, ) -> Response { let repo = UsersRepository::new(state); - let email = extract_email(&headers).unwrap(); - let user_data = repo.query_user_by_email(email).await.unwrap(); + let email = match extract_email(&headers) { + Some(email) => email, + None => return common_response(StatusCode::UNAUTHORIZED, "Unauthorized"), + }; + let user_data = match repo.query_user_by_email(email.clone()).await { + Ok(user) => user, + Err(_) => return common_response(StatusCode::NOT_FOUND, "User not found"), + }; if let Err((status, message)) = validate_request(&user) { return common_response(status, &message); } - let user_id = - make_thing(&ResourceEnum::Users.to_string(), &user_data.id.id.to_raw()); - let role_id = make_thing(&ResourceEnum::Roles.to_string(), ""); - let updated_user = UsersSchema { - id: user_id, - fullname: user.fullname, - email: user.email, - phone_number: user.phone_number, - - is_active: user.is_active, - - gender: user.gender, - birthdate: user.birthdate, - avatar: user.avatar, - - role: role_id, - updated_at: get_iso_date(), - ..Default::default() - }; + let updated_user = UsersSchema::update(user, user_data.id.id.to_raw()); match repo.query_update_user(updated_user).await { Ok(msg) => common_response(StatusCode::OK, &msg), Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()), @@ -183,23 +122,23 @@ impl UsersService { pub async fn set_user_active_status( state: &AppState, id: String, - status: UsersActiveInactiveRequestDto, + payload: UsersActiveInactiveRequestDto, ) -> Response { let repo = UsersRepository::new(state); let thing_id = make_thing(&ResourceEnum::Users.to_string(), &id); match repo.query_user_by_id(thing_id.id.to_raw()).await { - Ok(_) => match repo - .query_active_inactive_user_by_id( - id, - UsersActiveInactiveSchema { - is_active: status.is_active, - }, - ) - .await - { - Ok(msg) => common_response(StatusCode::OK, &msg), - Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()), - }, + Ok(user) if !user.is_deleted => { + let patch = UsersSchema { + id: user.id.clone(), + is_active: payload.is_active, + ..UsersSchema::from(user) + }; + match repo.query_update_user(patch).await { + Ok(msg) => common_response(StatusCode::OK, &msg), + Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()), + } + } + Ok(_) => common_response(StatusCode::NOT_FOUND, "User not found"), Err(err) => common_response(StatusCode::BAD_REQUEST, &err.to_string()), } } @@ -207,10 +146,41 @@ impl UsersService { pub async fn update_user_password( state: &AppState, email: String, - new_password: UsersSetNewPasswordSchema, + payload: UsersSetNewPasswordRequestDto, ) -> Response { let repo = UsersRepository::new(state); - match repo.query_update_password_user(email, new_password).await { + let user = match 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 verify_result = match verify_password(&payload.old_password, &user.password) + { + Ok(result) => result, + Err(_) => { + return common_response( + StatusCode::BAD_REQUEST, + "Old password is incorrect", + ); + } + }; + if !verify_result { + return common_response(StatusCode::BAD_REQUEST, "Old password is incorrect"); + } + let new_password = match hash_password(&payload.password) { + Ok(pw) => pw, + Err(_) => { + return common_response( + StatusCode::INTERNAL_SERVER_ERROR, + "Failed to hash password", + ); + } + }; + let patch = UsersSchema { + id: user.id.clone(), + password: new_password, + ..Default::default() + }; + match repo.query_update_user(patch).await { Ok(msg) => common_response(StatusCode::OK, &msg), Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()), } diff --git a/imphnen-utils/src/make_thing.rs b/imphnen-utils/src/make_thing.rs index 39986ab..735bf1a 100644 --- a/imphnen-utils/src/make_thing.rs +++ b/imphnen-utils/src/make_thing.rs @@ -3,3 +3,7 @@ use surrealdb::sql::Thing; pub fn make_thing(table: &str, id: &str) -> Thing { Thing::from((table, id)) } + +pub fn make_thing_str(table: &str, id: &str) -> String { + format!("{}:⟨{}⟩", table, id) +} diff --git a/imphnen-utils/src/query_builder.rs b/imphnen-utils/src/query_builder.rs index 33e1a3e..df6eab0 100644 --- a/imphnen-utils/src/query_builder.rs +++ b/imphnen-utils/src/query_builder.rs @@ -1,4 +1,7 @@ use imphnen_libs::MetaRequestDto; +use surrealdb::engine::remote::ws::Client; +use surrealdb::method::Query; +use surrealdb::sql::Thing; pub struct ListQueryBuilder { resource: String, @@ -135,3 +138,106 @@ impl ListQueryBuilder { ) } } + +pub struct DetailQueryBuilder { + resource: String, + id: Option, + thing: Option, + where_field: Option, + where_value: Option, + select_fields: Vec, + fetch_fields: Vec, +} + +impl DetailQueryBuilder { + pub fn new(resource: impl Into) -> Self { + Self { + resource: resource.into(), + id: None, + thing: None, + where_field: None, + where_value: None, + select_fields: vec![], + fetch_fields: vec![], + } + } + + pub fn with_id(mut self, id: impl Into) -> Self { + if self.where_field.is_some() || self.thing.is_some() { + panic!("Cannot use with_id() after with_where() or with_thing()"); + } + self.id = Some(id.into()); + self + } + + pub fn with_thing(mut self, thing: &Thing) -> Self { + if self.id.is_some() || self.where_field.is_some() { + panic!("Cannot use with_thing() after with_id() or with_where()"); + } + self.thing = Some(thing.to_string()); // app_users:uuid + self.resource = thing.tb.clone(); // update resource dari thing + self + } + + pub fn with_where(mut self, field: impl Into) -> Self { + if self.id.is_some() || self.thing.is_some() { + panic!("Cannot use with_where() after with_id() or with_thing()"); + } + self.where_field = Some(field.into()); + self + } + + pub fn where_value(mut self, value: impl Into) -> Self { + self.where_value = Some(value.into()); + self + } + + 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_fetch(mut self, field: impl Into) -> Self { + self.fetch_fields.push(field.into()); + self + } + + pub fn build(&self) -> String { + let select_clause = if self.select_fields.is_empty() { + "*".to_string() + } else { + self.select_fields.join(", ") + }; + + let fetch_clause = if self.fetch_fields.is_empty() { + String::new() + } else { + format!("FETCH {}", self.fetch_fields.join(", ")) + }; + + let from_clause = if let Some(thing) = &self.thing { + thing.to_string() + } else if let Some(id) = &self.id { + format!("{}:⟨{}⟩", self.resource, id) + } else if let (Some(field), Some(_)) = (&self.where_field, &self.where_value) { + format!("{} WHERE {} = $value", self.resource, field) + } else { + panic!( + "You must set one of with_id(), with_thing(), or with_where()+where_value()" + ); + }; + + format!( + "SELECT {} FROM {} {}", + select_clause, from_clause, fetch_clause + ) + } + + pub fn apply_bindings<'q>(&self, query: Query<'q, Client>) -> Query<'q, Client> { + if let (Some(_), Some(value)) = (&self.where_field, &self.where_value) { + query.bind(("value", value.clone())) + } else { + query + } + } +} From d97095a38da55b057f129772c7a2b986495634fb Mon Sep 17 00:00:00 2001 From: Maulana Sodiqin Date: Tue, 20 May 2025 00:26:24 +0700 Subject: [PATCH 3/4] chore: cleaning code --- imphnen-backend/src/bin/seed_permissions.rs | 24 ++--- .../v1/gacha_item/gacha_item_repository.rs | 2 + imphnen-gateway/src/docs.rs | 11 +- imphnen-iam/src/v1/auth/auth_dto.rs | 62 +++-------- imphnen-iam/src/v1/auth/auth_repository.rs | 16 +-- imphnen-iam/src/v1/auth/auth_service.rs | 100 +++++++++--------- .../v1/permissions/permissions_repository.rs | 32 +++--- .../src/v1/permissions/permissions_schema.rs | 4 +- imphnen-iam/src/v1/users/users_dto.rs | 22 ++-- imphnen-iam/src/v1/users/users_service.rs | 4 +- imphnen-middleware/src/auth_middleware/mod.rs | 4 +- tests/src/iam/auth/auth_repository_test.rs | 11 +- tests/src/iam/roles/roles_controller_test.rs | 8 +- tests/src/iam/roles/roles_repository_test.rs | 2 + tests/src/iam/users/users_repository_test.rs | 87 +-------------- tests/src/lib.rs | 1 - 16 files changed, 139 insertions(+), 251 deletions(-) diff --git a/imphnen-backend/src/bin/seed_permissions.rs b/imphnen-backend/src/bin/seed_permissions.rs index 3f878d8..e412651 100644 --- a/imphnen-backend/src/bin/seed_permissions.rs +++ b/imphnen-backend/src/bin/seed_permissions.rs @@ -25,8 +25,8 @@ async fn main() -> Result<(), Box> { ( "0269ed71-0ae0-4c43-ad29-e3d861d8f9a0", "Create Permissions", - None, - None, + Some("2025-01-29T05:11:01.265+00"), + Some("2025-01-29T05:11:01.265001+00"), ), ( "299cb4d5-6556-4cc9-b6c1-32e6d31e0f9b", @@ -37,20 +37,20 @@ async fn main() -> Result<(), Box> { ( "319ee593-ff0a-4f29-bbaf-9feb3174a3a2", "Create Roles", - None, - None, + Some("2025-01-29T05:11:01.265+00"), + Some("2025-01-29T05:11:01.265001+00"), ), ( "319ee593-ff0a-4f29-bbaf-9feb3174a3a6", "Read Detail Users", - None, - None, + Some("2025-01-29T05:11:01.265+00"), + Some("2025-01-29T05:11:01.265001+00"), ), ( "35b0d992-65c8-4b62-b030-e6e0320e4048", "Delete Roles", - None, - None, + Some("2025-01-29T05:34:40.621554+00"), + Some("2025-01-29T05:34:40.621555+00"), ), ( "4da8b434-89f9-4d91-85ae-eebd63cdbeda", @@ -91,8 +91,8 @@ async fn main() -> Result<(), Box> { ( "9164ca6e-c7e3-4238-a15f-f36ab9577e7e", "Read List Roles", - None, - None, + Some("2025-01-29T05:34:40.621554+00"), + Some("2025-01-29T05:34:40.621555+00"), ), ( "96df0689-2ae9-4894-bf00-837c19415e5c", @@ -109,8 +109,8 @@ async fn main() -> Result<(), Box> { ( "a00d5608-4c48-4542-845c-dfe004687022", "Update Roles", - None, - None, + Some("2025-01-29T05:34:40.621554+00"), + Some("2025-01-29T05:34:40.621555+00"), ), ( "b2dc3928-86ba-4c59-a03d-0b57d5183ebc", diff --git a/imphnen-gacha/src/v1/gacha_item/gacha_item_repository.rs b/imphnen-gacha/src/v1/gacha_item/gacha_item_repository.rs index 5c608cb..436a899 100644 --- a/imphnen-gacha/src/v1/gacha_item/gacha_item_repository.rs +++ b/imphnen-gacha/src/v1/gacha_item/gacha_item_repository.rs @@ -28,6 +28,8 @@ impl<'a> GachaItemRepository<'a> { &meta, conditions, None, + "name", + None, ) .await } diff --git a/imphnen-gateway/src/docs.rs b/imphnen-gateway/src/docs.rs index 9f9ab3f..61751fe 100644 --- a/imphnen-gateway/src/docs.rs +++ b/imphnen-gateway/src/docs.rs @@ -1,6 +1,5 @@ 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 +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 }; use utoipa::{ openapi::security::{Http, HttpAuthScheme, SecurityScheme}, @@ -48,18 +47,18 @@ use utoipa::{ AuthNewPasswordRequestDto, AuthRefreshTokenRequestDto, ResponseSuccessDto, - RolesItemDto, + RolesListItemDto, RolesRequestCreateDto, RolesRequestUpdateDto, PermissionsRequestDto, PermissionsItemDto, - UsersItemDto, + UsersDetailItemDto, UsersListItemDto, UsersUpdateRequestDto, UsersCreateRequestDto, ResponseSuccessDto, - ResponseListSuccessDto>, - ResponseSuccessDto, + ResponseListSuccessDto>, + ResponseSuccessDto, ResponseListSuccessDto>, ResponseSuccessDto, ResponseListSuccessDto>, diff --git a/imphnen-iam/src/v1/auth/auth_dto.rs b/imphnen-iam/src/v1/auth/auth_dto.rs index 90374c4..d4f0883 100644 --- a/imphnen-iam/src/v1/auth/auth_dto.rs +++ b/imphnen-iam/src/v1/auth/auth_dto.rs @@ -1,25 +1,29 @@ -use crate::RolesItemDto; use lazy_static::lazy_static; use regex::Regex; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; use validator::{Validate, ValidationError}; +use crate::UsersDetailItemDto; + 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> { - let has_uppercase = password.chars().any(|c| c.is_ascii_uppercase()); - let has_lowercase = password.chars().any(|c| c.is_ascii_lowercase()); - let has_digit = password.chars().any(|c| c.is_ascii_digit()); - let has_special = password.chars().any(|c| "@$!%*?&".contains(c)); + let has_uppercase = password.chars().any(|c| c.is_ascii_uppercase()); + let has_lowercase = password.chars().any(|c| c.is_ascii_lowercase()); + let has_digit = password.chars().any(|c| c.is_ascii_digit()); + let has_special = password.chars().any(|c| "@$!%*?&".contains(c)); - if has_uppercase && has_lowercase && has_digit && has_special { - Ok(()) - } else { - Err(ValidationError::new("complexity")) - } + if has_uppercase && has_lowercase && has_digit && has_special { + Ok(()) + } else { + Err(ValidationError::new("complexity")) + } } #[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)] @@ -33,23 +37,10 @@ pub struct AuthLoginRequestDto { 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, - pub phone_number: String, - pub is_active: bool, - pub gender: Option, - pub birthdate: Option, -} - #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] pub struct AuthLoginResponsetDto { pub token: TokenDto, - pub user: AuthUserItemDto, + pub user: UsersDetailItemDto, } #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] @@ -77,28 +68,7 @@ pub struct AuthRegisterRequestDto { #[validate(length(min = 2, message = "Fullname at least have 2 character"))] pub fullname: String, #[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, - #[validate(length( - max = 4, - message = "Referal code cannot be more than 4 character" - ))] - pub referral_code: Option, - pub referred_by: Option, -} - -#[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)] diff --git a/imphnen-iam/src/v1/auth/auth_repository.rs b/imphnen-iam/src/v1/auth/auth_repository.rs index 7fa4076..9b44161 100644 --- a/imphnen-iam/src/v1/auth/auth_repository.rs +++ b/imphnen-iam/src/v1/auth/auth_repository.rs @@ -1,6 +1,6 @@ use super::AuthOtpSchema; -use crate::{make_thing, AppState, ResourceEnum, UsersItemDtoRaw}; -use anyhow::{anyhow, bail, Result}; +use crate::{AppState, ResourceEnum, UsersDetailQueryDto, make_thing}; +use anyhow::{Result, anyhow, bail}; use chrono::{Duration, Utc}; pub struct AuthRepository<'a> { @@ -12,7 +12,7 @@ impl<'a> AuthRepository<'a> { Self { state } } - pub async fn query_store_user(&self, user: UsersItemDtoRaw) -> Result { + pub async fn query_store_user(&self, user: UsersDetailQueryDto) -> Result { if user.email.trim().is_empty() { bail!("Email is required"); } @@ -22,11 +22,11 @@ impl<'a> AuthRepository<'a> { let _ = self .state .surrealdb_mem - .delete::>((table.clone(), user_id.clone())) + .delete::>((table.clone(), user_id.clone())) .await?; let mut user_to_store = user.clone(); user_to_store.id = id.clone(); - let record: Option = self + let record: Option = self .state .surrealdb_mem .create((table, user_id)) @@ -41,8 +41,8 @@ impl<'a> AuthRepository<'a> { pub async fn query_get_stored_user( &self, email: String, - ) -> Result { - let user: Option = self + ) -> Result { + let user: Option = self .state .surrealdb_mem .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 { - let record: Option = self + let record: Option = self .state .surrealdb_mem .delete((ResourceEnum::UsersCache.to_string(), email)) diff --git a/imphnen-iam/src/v1/auth/auth_service.rs b/imphnen-iam/src/v1/auth/auth_service.rs index 76df782..c59e0a0 100644 --- a/imphnen-iam/src/v1/auth/auth_service.rs +++ b/imphnen-iam/src/v1/auth/auth_service.rs @@ -1,15 +1,15 @@ use super::{ AuthLoginRequestDto, AuthLoginResponsetDto, AuthNewPasswordRequestDto, AuthRefreshTokenRequestDto, AuthRegisterRequestDto, AuthRepository, - AuthResendOtpRequestDto, AuthUserItemDto, AuthVerifyEmailRequestDto, TokenDto, + AuthResendOtpRequestDto, AuthVerifyEmailRequestDto, TokenDto, }; 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, hash_password, make_thing, send_email, success_response, validate_request, - verify_password, AppState, Env, ResourceEnum, ResponseSuccessDto, RolesEnum, - RolesItemDto, RolesRepository, UsersActiveInactiveSchema, UsersRepository, - UsersSchema, UsersSetNewPasswordSchema, + verify_password, }; use axum::{http::StatusCode, response::Response}; use surrealdb::Uuid; @@ -27,7 +27,6 @@ impl AuthService { let user_repo = UsersRepository::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 { Ok(user) => { @@ -54,7 +53,7 @@ impl AuthService { return common_response( StatusCode::INTERNAL_SERVER_ERROR, "Failed to generate access token", - ) + ); } }; @@ -64,34 +63,13 @@ impl AuthService { return common_response( StatusCode::INTERNAL_SERVER_ERROR, "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 { data: AuthLoginResponsetDto { - user: AuthUserItemDto { - 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, - }, - }, + user: UsersDetailItemDto::from(&user), token: TokenDto { access_token, refresh_token, @@ -116,6 +94,7 @@ impl AuthService { if let Err((status, message)) = validate_request(&payload) { return common_response(status, &message); } + let user_repo = UsersRepository::new(state); let auth_repo = AuthRepository::new(state); let role_repo = RolesRepository::new(state); @@ -126,6 +105,7 @@ impl AuthService { Ok(role) => role, Err(_) => return common_response(StatusCode::BAD_REQUEST, "Role Not Found"), }; + if user_repo .query_user_by_email(payload.email.clone()) .await @@ -133,6 +113,7 @@ impl AuthService { { return common_response(StatusCode::BAD_REQUEST, "User already exists"); } + let hashed_password = match hash_password(&payload.password) { Ok(hash) => hash, Err(_) => { @@ -142,16 +123,16 @@ impl AuthService { ); } }; + let new_user = AuthRegisterRequestDto { email: payload.email, password: hashed_password, fullname: payload.fullname, - student_type: payload.student_type, phone_number: payload.phone_number, - referral_code: payload.referral_code, - referred_by: payload.referred_by, }; + let otp = generate_otp::OtpManager::generate_otp(); + match auth_repo .query_store_otp(new_user.email.clone(), otp.clone()) .await @@ -169,11 +150,13 @@ impl AuthService { return common_response(StatusCode::INTERNAL_SERVER_ERROR, &err.to_string()); } } + let role_thing = make_thing(&ResourceEnum::Roles.to_string(), &role.id); let user_thing = make_thing( &ResourceEnum::Users.to_string(), &Uuid::new_v4().to_string(), ); + match user_repo .query_create_user(UsersSchema { id: user_thing, @@ -202,14 +185,18 @@ impl AuthService { if let Err((status, message)) = validate_request(&payload) { return common_response(status, &message); } - + 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"); } let auth_repo = AuthRepository::new(state); - let _ = auth_repo.query_get_stored_otp(payload.email.clone()).await; + let _ = auth_repo.query_get_stored_otp(payload.email.clone()).await; let otp = generate_otp::OtpManager::generate_otp(); let message = format!("Your OTP code is {}", otp); @@ -273,10 +260,10 @@ impl AuthService { let user = match user_result { Ok(user) => user, 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) => { - 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) { @@ -285,7 +272,7 @@ impl AuthService { return common_response( StatusCode::INTERNAL_SERVER_ERROR, "Failed to generate access token", - ) + ); } }; let env = Env::new(); @@ -310,15 +297,18 @@ impl AuthService { let user_repo = UsersRepository::new(state); let auth_repo = AuthRepository::new(state); 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 { Ok(stored_otp) => match stored_otp == payload.otp { - true => match user_repo - .query_active_inactive_user( - email.clone(), - UsersActiveInactiveSchema { is_active: true }, - ) - .await - { + true => match user_repo.query_update_user(patch).await { Ok(_) => match auth_repo.query_delete_stored_otp(email).await { Ok(_) => common_response(StatusCode::OK, "Email verified successfully"), Err(e) => { @@ -346,7 +336,7 @@ impl AuthService { if let Err((status, message)) = validate_request(&payload) { 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()) { Some(email) => email, None => { @@ -362,12 +352,18 @@ impl AuthService { ); } }; - match user_repo - .query_update_password_user(email, UsersSetNewPasswordSchema { password }) - .await - { + let user = match 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(), + password, + ..Default::default() + }; + match repo.query_update_user(patch).await { 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()), } } } diff --git a/imphnen-iam/src/v1/permissions/permissions_repository.rs b/imphnen-iam/src/v1/permissions/permissions_repository.rs index ce3e8f4..1d1b0cf 100644 --- a/imphnen-iam/src/v1/permissions/permissions_repository.rs +++ b/imphnen-iam/src/v1/permissions/permissions_repository.rs @@ -1,9 +1,9 @@ -use super::{PermissionsItemDto, PermissionsItemDtoRaw, PermissionsSchema}; +use super::{PermissionsItemDto, PermissionsSchema}; use crate::{ - get_id, make_thing, query_list_with_meta, AppState, MetaRequestDto, ResourceEnum, - ResponseListSuccessDto, + AppState, MetaRequestDto, ResourceEnum, ResponseListSuccessDto, get_id, + make_thing, query_list_with_meta, }; -use anyhow::{bail, Result}; +use anyhow::{Result, bail}; use imphnen_utils::extract_id; pub struct PermissionsRepository<'a> { @@ -27,14 +27,17 @@ impl<'a> PermissionsRepository<'a> { let filter_by = meta.filter_by.as_ref().unwrap(); conditions.push(format!("{} = $filter", filter_by)); } - let raw_result: ResponseListSuccessDto> = query_list_with_meta( - &self.state.surrealdb_ws, - &ResourceEnum::Permissions.to_string(), - &meta, - conditions, - None, - ) - .await?; + let raw_result: ResponseListSuccessDto> = + query_list_with_meta( + &self.state.surrealdb_ws, + &ResourceEnum::Permissions.to_string(), + &meta, + conditions, + None, + "name", + None, + ) + .await?; let transformed_data = raw_result .data .into_iter() @@ -69,9 +72,7 @@ impl<'a> PermissionsRepository<'a> { &self, id: String, ) -> Result { - let raw_result = self - .query_permission_by_id(id.clone()) - .await?; + let raw_result = self.query_permission_by_id(id.clone()).await?; let transformed_data = PermissionsItemDto { id: extract_id(&raw_result.id), name: raw_result.name, @@ -81,7 +82,6 @@ impl<'a> PermissionsRepository<'a> { Ok(transformed_data) } - pub async fn query_permission_by_name( &self, name: String, diff --git a/imphnen-iam/src/v1/permissions/permissions_schema.rs b/imphnen-iam/src/v1/permissions/permissions_schema.rs index ba08b3a..274743a 100644 --- a/imphnen-iam/src/v1/permissions/permissions_schema.rs +++ b/imphnen-iam/src/v1/permissions/permissions_schema.rs @@ -1,6 +1,6 @@ -use crate::{make_thing, ResourceEnum}; +use crate::{ResourceEnum, make_thing}; use serde::{Deserialize, Serialize}; -use surrealdb::{sql::Thing, Uuid}; +use surrealdb::{Uuid, sql::Thing}; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PermissionsSchema { diff --git a/imphnen-iam/src/v1/users/users_dto.rs b/imphnen-iam/src/v1/users/users_dto.rs index 41c29f6..4e3d8b0 100644 --- a/imphnen-iam/src/v1/users/users_dto.rs +++ b/imphnen-iam/src/v1/users/users_dto.rs @@ -95,19 +95,19 @@ pub struct UsersDetailItemDto { } impl UsersDetailItemDto { - pub fn from(dto: UsersDetailQueryDto) -> Self { + pub fn from(dto: &UsersDetailQueryDto) -> Self { Self { - id: dto.id.id.to_raw(), + id: dto.id.id.to_raw().clone(), role: RolesDetailItemDto::from(&dto.role), - fullname: dto.fullname, - email: dto.email, - avatar: dto.avatar, - phone_number: dto.phone_number, - is_active: dto.is_active, - gender: dto.gender, - birthdate: dto.birthdate, - created_at: dto.created_at, - updated_at: dto.updated_at, + fullname: dto.fullname.clone(), + email: dto.email.clone(), + avatar: dto.avatar.clone(), + phone_number: dto.phone_number.clone(), + is_active: dto.is_active.clone(), + gender: dto.gender.clone(), + birthdate: dto.birthdate.clone(), + created_at: dto.created_at.clone(), + updated_at: dto.updated_at.clone(), } } } diff --git a/imphnen-iam/src/v1/users/users_service.rs b/imphnen-iam/src/v1/users/users_service.rs index f4f41f9..b401fc6 100644 --- a/imphnen-iam/src/v1/users/users_service.rs +++ b/imphnen-iam/src/v1/users/users_service.rs @@ -34,7 +34,7 @@ impl UsersService { let repo = UsersRepository::new(state); match repo.query_user_by_id(id).await { 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"), Err(e) => common_response(StatusCode::NOT_FOUND, &e.to_string()), @@ -49,7 +49,7 @@ impl UsersService { }; match repo.query_user_by_email(email).await { 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"), Err(e) => common_response(StatusCode::NOT_FOUND, &e.to_string()), diff --git a/imphnen-middleware/src/auth_middleware/mod.rs b/imphnen-middleware/src/auth_middleware/mod.rs index d91068b..f5c4f07 100644 --- a/imphnen-middleware/src/auth_middleware/mod.rs +++ b/imphnen-middleware/src/auth_middleware/mod.rs @@ -2,7 +2,7 @@ use axum::{ Extension, extract::Request, http::StatusCode, middleware::Next, response::Response, }; -use imphnen_iam::{UsersItemDtoRaw, UsersRepository}; +use imphnen_iam::{UsersDetailQueryDto, UsersRepository}; use imphnen_libs::AppState; use imphnen_utils::{common_response, extract_email}; use std::convert::Infallible; @@ -23,7 +23,7 @@ pub async fn auth_middleware( } }; let repository = UsersRepository::new(&state); - let user: Option = + let user: Option = match repository.query_user_by_email(email).await { Ok(user) => Some(user), Err(err) => { diff --git a/tests/src/iam/auth/auth_repository_test.rs b/tests/src/iam/auth/auth_repository_test.rs index c16026d..f7dce4b 100644 --- a/tests/src/iam/auth/auth_repository_test.rs +++ b/tests/src/iam/auth/auth_repository_test.rs @@ -1,10 +1,11 @@ #[cfg(test)] mod auth_repository_test { use crate::{ - create_mock_app_state, get_iso_date, make_thing, AuthOtpSchema, AuthRepository, - ResourceEnum, RolesItemDtoRaw, UsersItemDtoRaw, UsersRepository, UsersSchema, + AuthOtpSchema, AuthRepository, ResourceEnum, UsersRepository, UsersSchema, + create_mock_app_state, get_iso_date, make_thing, }; use chrono::{Duration, Utc}; + use imphnen_iam::{RolesDetailQueryDto, UsersDetailQueryDto}; use surrealdb::Uuid; fn create_mock_user(email: &str) -> UsersSchema { @@ -47,7 +48,7 @@ mod auth_repository_test { let state = create_mock_app_state().await; let auth_repo = AuthRepository::new(&state); let email = "delete_me@example.com".to_string(); - let mock_user = UsersItemDtoRaw { + let mock_user = UsersDetailQueryDto { id: make_thing(&ResourceEnum::UsersCache.to_string(), &email), fullname: "Test User".into(), email: email.clone(), @@ -56,7 +57,7 @@ mod auth_repository_test { is_active: true, gender: None, birthdate: None, - role: RolesItemDtoRaw { + role: RolesDetailQueryDto { id: make_thing("app_roles", &Uuid::new_v4().to_string()), name: "Dummy Role".into(), permissions: vec![], @@ -69,7 +70,7 @@ mod auth_repository_test { created_at: get_iso_date(), updated_at: get_iso_date(), }; - let _: Option = state + let _: Option = state .surrealdb_mem .create((ResourceEnum::UsersCache.to_string(), email.clone())) .content(mock_user) diff --git a/tests/src/iam/roles/roles_controller_test.rs b/tests/src/iam/roles/roles_controller_test.rs index 937e177..3979817 100644 --- a/tests/src/iam/roles/roles_controller_test.rs +++ b/tests/src/iam/roles/roles_controller_test.rs @@ -1,11 +1,11 @@ use crate::AppState; use crate::{ - create_mock_app_state, roles_router, v1::roles_repository::RolesRepository, - RolesRequestCreateDto, RolesRequestUpdateDto, + RolesRequestCreateDto, RolesRequestUpdateDto, create_mock_app_state, roles_router, + v1::roles_repository::RolesRepository, }; -use axum::http::StatusCode; use axum::Extension; use axum::Router; +use axum::http::StatusCode; use axum_test::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 { name: Some("Updated Role".into()), permissions: Some(vec![]), + overwrite: None, }; let res = server .put(&format!("/v1/roles/update/{}", id)) @@ -131,6 +132,7 @@ async fn test_put_update_nonexistent_role_should_return_404() { let payload = RolesRequestUpdateDto { name: Some("Does Not Exist".into()), permissions: Some(vec![]), + overwrite: None, }; let res = server .put("/v1/roles/update/nonexistent-id") diff --git a/tests/src/iam/roles/roles_repository_test.rs b/tests/src/iam/roles/roles_repository_test.rs index a7de09e..adcea17 100644 --- a/tests/src/iam/roles/roles_repository_test.rs +++ b/tests/src/iam/roles/roles_repository_test.rs @@ -100,6 +100,7 @@ async fn test_query_update_role_should_update_name_and_permissions() { let update_payload = RolesRequestUpdateDto { name: Some("Updated Role Name".into()), permissions: Some(vec![new_perm_id.clone()]), + overwrite: None, }; let update_result = repo .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 { name: Some("Updated Role Name".into()), permissions: None, + overwrite: None, }; let update_res = repo .query_update_role(existing_id.clone(), update_payload) diff --git a/tests/src/iam/users/users_repository_test.rs b/tests/src/iam/users/users_repository_test.rs index 3b9fbb3..ff1b9cd 100644 --- a/tests/src/iam/users/users_repository_test.rs +++ b/tests/src/iam/users/users_repository_test.rs @@ -1,8 +1,5 @@ -use crate::{create_mock_app_state, create_test_user, RolesRepository}; -use crate::{ - MetaRequestDto, UsersActiveInactiveSchema, UsersRepository, - UsersSetNewPasswordSchema, -}; +use crate::{MetaRequestDto, UsersRepository}; +use crate::{RolesRepository, create_mock_app_state, create_test_user}; async fn get_role_id(state: &crate::AppState) -> String { RolesRepository::new(state) @@ -31,35 +28,6 @@ async fn test_create_and_get_user() { 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] async fn test_query_user_list_with_pagination_and_filter() { 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); } -#[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] async fn test_query_delete_user() { let app_state = create_mock_app_state().await; diff --git a/tests/src/lib.rs b/tests/src/lib.rs index eded98f..863596d 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -1,6 +1,5 @@ pub use imphnen_entities::*; pub use imphnen_iam::*; -pub use imphnen_utils::*; #[cfg(test)] pub mod iam; From 1e5ed26f764c0cfd9a2ba5dd0d84c102fe8b05d4 Mon Sep 17 00:00:00 2001 From: Maulana Sodiqin Date: Tue, 20 May 2025 01:49:50 +0700 Subject: [PATCH 4/4] chore: cleaning code --- Cargo.lock | 132 ++++++++++++++++++ Cargo.toml | 1 + imphnen-backend/Cargo.toml | 2 + imphnen-backend/src/bin/api.rs | 2 + .../src/bin/seed_roles_permissions.rs | 70 +++++----- imphnen-backend/src/bin/seed_users.rs | 31 +--- .../v1/gacha_item/gacha_item_repository.rs | 1 + .../v1/permissions/permissions_controller.rs | 11 +- .../v1/permissions/permissions_repository.rs | 8 +- .../src/v1/permissions/permissions_schema.rs | 13 ++ imphnen-iam/src/v1/roles/roles_repository.rs | 30 ++-- imphnen-iam/src/v1/roles/roles_schema.rs | 12 +- imphnen-iam/src/v1/users/users_dto.rs | 24 +++- imphnen-iam/src/v1/users/users_repository.rs | 123 ++-------------- imphnen-utils/src/bind_filter.rs | 8 +- imphnen-utils/src/crud.rs | 8 -- imphnen-utils/src/lib.rs | 2 - imphnen-utils/src/query_builder.rs | 20 ++- imphnen-utils/src/query_list.rs | 12 +- 19 files changed, 274 insertions(+), 236 deletions(-) delete mode 100644 imphnen-utils/src/crud.rs diff --git a/Cargo.lock b/Cargo.lock index 48f9341..65c2a78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -115,6 +115,56 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +dependencies = [ + "anstyle", + "once_cell", + "windows-sys 0.59.0", +] + [[package]] name = "any_ascii" version = "0.3.2" @@ -796,6 +846,12 @@ dependencies = [ "inout", ] +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1097,6 +1153,29 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -1854,8 +1933,10 @@ dependencies = [ "axum", "axum-test", "chrono", + "env_logger", "imphnen-entities", "imphnen-gateway", + "imphnen-iam", "imphnen-libs", "imphnen-utils", "lazy_static", @@ -2076,6 +2157,12 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.10.5" @@ -2109,6 +2196,30 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jiff" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02000660d30638906021176af16b17498bd0d12813dbfe7b276d8bc7f3c0806" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c30758ddd7188629c6713fc45d1188af4f44c90582311d0c8d8c9907f60c48" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "js-sys" version = "0.3.77" @@ -2845,6 +2956,21 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -4741,6 +4867,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "utoipa" version = "5.3.1" diff --git a/Cargo.toml b/Cargo.toml index 58e6c7e..8d676d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ axum-test = "17.2.0" fancy-regex = "0.14.0" futures = "0.3.31" tower = "0.5.2" +env_logger = "0.11.8" [profile.release] lto = "fat" diff --git a/imphnen-backend/Cargo.toml b/imphnen-backend/Cargo.toml index 0409d39..6d4ff86 100644 --- a/imphnen-backend/Cargo.toml +++ b/imphnen-backend/Cargo.toml @@ -8,6 +8,7 @@ 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-iam = { version = "0.1.0", path = "../imphnen-iam" } axum.workspace = true serde.workspace = true serde_json.workspace = true @@ -23,3 +24,4 @@ chrono.workspace = true anyhow.workspace = true tower-http.workspace = true utoipa-swagger-ui.workspace = true +env_logger.workspace = true diff --git a/imphnen-backend/src/bin/api.rs b/imphnen-backend/src/bin/api.rs index a261ef9..b6b3c3a 100644 --- a/imphnen-backend/src/bin/api.rs +++ b/imphnen-backend/src/bin/api.rs @@ -1,8 +1,10 @@ +use env_logger; use imphnen_gateway::gateway_service; use imphnen_libs::axum_init; #[tokio::main] async fn main() { + env_logger::init(); axum_init(|surrealdb_ws, surrealdb_mem| async { gateway_service(surrealdb_ws, surrealdb_mem).await }) diff --git a/imphnen-backend/src/bin/seed_roles_permissions.rs b/imphnen-backend/src/bin/seed_roles_permissions.rs index 71466d5..90f5484 100644 --- a/imphnen-backend/src/bin/seed_roles_permissions.rs +++ b/imphnen-backend/src/bin/seed_roles_permissions.rs @@ -1,4 +1,4 @@ -use imphnen_utils::{get_iso_date, make_thing, Env}; +use imphnen_iam::{get_iso_date, make_thing, Env}; use std::error::Error; use surrealdb::{engine::remote::ws::Ws, opt::auth::Root, Surreal}; @@ -17,43 +17,41 @@ async fn main() -> Result<(), Box> { .use_db(env.surrealdb_dbname) .await?; - let role_permissions = vec![ - ( - "50133429-f4b1-4249-9f97-7b86e6ee9d86", - vec![ - "7c15e31d-36e2-49f9-97db-138c03fb0cf6", - "319ee593-ff0a-4f29-bbaf-9feb3174a3a6", - ], - ), - ( - "f6b03f25-e416-4893-ac88-caaa690afb07", - vec![ - "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", - ], - ), + let admin_permissions = vec![ + "023e2dfe-93c3-4008-94a8-b5dff403f73b", + "0269ed71-0ae0-4c43-ad29-e3d861d8f9a0", + "299cb4d5-6556-4cc9-b6c1-32e6d31e0f9b", + "319ee593-ff0a-4f29-bbaf-9feb3174a3a2", + "319ee593-ff0a-4f29-bbaf-9feb3174a3a6", + "35b0d992-65c8-4b62-b030-e6e0320e4048", + "4da8b434-89f9-4d91-85ae-eebd63cdbeda", + "73888d18-b3e9-4f62-95a5-ba2c0d69fccb", + "7c15e31d-36e2-49f9-97db-138c03fb0cf6", + "7d4b1379-4960-416a-b045-98cd82c0cac9", + "8195eeb8-e64f-4172-aa57-596492c84a72", + "81eba91d-b8ab-44b9-bbfe-4e6da2f98952", + "9164ca6e-c7e3-4238-a15f-f36ab9577e7e", + "96df0689-2ae9-4894-bf00-837c19415e5c", + "98b3dc4c-0124-461f-afcd-166637c5e6e8", + "a00d5608-4c48-4542-845c-dfe004687022", + "b2dc3928-86ba-4c59-a03d-0b57d5183ebc", + "dad435cf-042c-41bd-a946-cea61ed2ffbc", ]; - for (role_id, permission_ids) in role_permissions { - let permission_refs: Vec<_> = permission_ids - .into_iter() - .map(|perm_id| make_thing("app_permissions", perm_id)) - .collect(); - db.query("UPDATE type::thing('app_roles', $role_id) SET permissions = $permissions, updated_at = $updated_at") - .bind(("role_id", role_id)) - .bind(("permissions", permission_refs)) - .bind(("updated_at", get_iso_date())) - .await?; - println!("✅ Updated permissions for role ID: {}", role_id); - } - println!("✅ All Roles permissions seeded"); + let admin_role_id = "f6b03f25-e416-4893-ac88-caaa690afb07"; + + let permission_refs_admin: Vec<_> = admin_permissions + .into_iter() + .map(|perm_id| make_thing("app_permissions", perm_id)) + .collect(); + + db.query("UPDATE type::thing('app_roles', $role_id) SET permissions = $permissions, updated_at = $updated_at WHERE is_deleted = false") + .bind(("role_id", admin_role_id)) + .bind(("permissions", permission_refs_admin)) + .bind(("updated_at", get_iso_date())) + .await?; + + println!("✅ All permissions successfully added to each role"); Ok(()) } diff --git a/imphnen-backend/src/bin/seed_users.rs b/imphnen-backend/src/bin/seed_users.rs index 01426bc..b5791b2 100644 --- a/imphnen-backend/src/bin/seed_users.rs +++ b/imphnen-backend/src/bin/seed_users.rs @@ -1,31 +1,8 @@ +use imphnen_iam::UsersSchema; use imphnen_utils::{get_iso_date, hash_password, Env}; -use serde::{Deserialize, Serialize}; use std::error::Error; use surrealdb::{engine::remote::ws::Ws, opt::auth::Root, sql::Thing, Surreal}; -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct UsersSchema { - pub id: Thing, - pub fullname: String, - pub email: String, - pub password: String, - pub avatar: Option, - pub phone_number: String, - pub referral_code: Option, - pub referred_by: Option, - pub identity_number: Option, - pub is_active: bool, - pub is_deleted: bool, - pub student_type: String, - pub religion: Option, - pub gender: Option, - pub birthdate: Option, - pub is_profile_completed: bool, - pub role: Thing, - pub created_at: String, - pub updated_at: String, -} - #[tokio::main] async fn main() -> Result<(), Box> { let env = Env::new(); @@ -68,16 +45,10 @@ async fn main() -> Result<(), Box> { password: hash_password("password").unwrap(), avatar: None, phone_number: "081234567890".into(), - referral_code: None, - referred_by: None, - identity_number: None, is_active: true, is_deleted: false, - student_type: "TNI".into(), - religion: None, gender: None, birthdate: None, - is_profile_completed: false, role: Thing::from(("app_roles", role_id)), created_at: get_iso_date(), updated_at: get_iso_date(), diff --git a/imphnen-gacha/src/v1/gacha_item/gacha_item_repository.rs b/imphnen-gacha/src/v1/gacha_item/gacha_item_repository.rs index 436a899..3f405a9 100644 --- a/imphnen-gacha/src/v1/gacha_item/gacha_item_repository.rs +++ b/imphnen-gacha/src/v1/gacha_item/gacha_item_repository.rs @@ -30,6 +30,7 @@ impl<'a> GachaItemRepository<'a> { None, "name", None, + None, ) .await } diff --git a/imphnen-iam/src/v1/permissions/permissions_controller.rs b/imphnen-iam/src/v1/permissions/permissions_controller.rs index 6cac43c..60708ec 100644 --- a/imphnen-iam/src/v1/permissions/permissions_controller.rs +++ b/imphnen-iam/src/v1/permissions/permissions_controller.rs @@ -1,23 +1,26 @@ use axum::{ + Extension, Json, extract::{Path, Query}, response::IntoResponse, - Extension, Json, }; use crate::{ + AppState, MessageResponseDto, MetaRequestDto, ResponseListSuccessDto, + ResponseSuccessDto, v1::{ permissions_dto::{PermissionsItemDto, PermissionsRequestDto}, permissions_service::PermissionsService, }, - AppState, MessageResponseDto, MetaRequestDto, ResponseListSuccessDto, - ResponseSuccessDto, }; -use super::{permissions_guard, PermissionsEnum}; +use super::{PermissionsEnum, permissions_guard}; #[utoipa::path( get, path = "/v1/permissions", + security( + ("Bearer" = []) + ), params( ("page" = Option, Query, description = "Page number"), ("per_page" = Option, Query, description = "Items per page"), diff --git a/imphnen-iam/src/v1/permissions/permissions_repository.rs b/imphnen-iam/src/v1/permissions/permissions_repository.rs index 1d1b0cf..66ba5b7 100644 --- a/imphnen-iam/src/v1/permissions/permissions_repository.rs +++ b/imphnen-iam/src/v1/permissions/permissions_repository.rs @@ -36,17 +36,13 @@ impl<'a> PermissionsRepository<'a> { None, "name", None, + None, ) .await?; let transformed_data = raw_result .data .into_iter() - .map(|permission| PermissionsItemDto { - id: extract_id(&permission.id), - name: permission.name, - created_at: permission.created_at, - updated_at: permission.updated_at, - }) + .map(|permission| PermissionsSchema::list(&permission)) .collect(); Ok(ResponseListSuccessDto { data: transformed_data, diff --git a/imphnen-iam/src/v1/permissions/permissions_schema.rs b/imphnen-iam/src/v1/permissions/permissions_schema.rs index 274743a..ef86d95 100644 --- a/imphnen-iam/src/v1/permissions/permissions_schema.rs +++ b/imphnen-iam/src/v1/permissions/permissions_schema.rs @@ -2,6 +2,8 @@ use crate::{ResourceEnum, make_thing}; use serde::{Deserialize, Serialize}; use surrealdb::{Uuid, sql::Thing}; +use super::PermissionsItemDto; + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PermissionsSchema { pub id: Thing, @@ -25,3 +27,14 @@ impl Default for PermissionsSchema { } } } + +impl PermissionsSchema { + pub fn list(&self) -> PermissionsItemDto { + PermissionsItemDto { + id: self.id.id.to_raw(), + name: self.name.clone(), + created_at: self.created_at.clone(), + updated_at: self.updated_at.clone(), + } + } +} diff --git a/imphnen-iam/src/v1/roles/roles_repository.rs b/imphnen-iam/src/v1/roles/roles_repository.rs index fcae8da..77c20d8 100644 --- a/imphnen-iam/src/v1/roles/roles_repository.rs +++ b/imphnen-iam/src/v1/roles/roles_repository.rs @@ -35,27 +35,21 @@ impl<'a> RolesRepository<'a> { conditions.push(format!("{} = $filter", filter_by)); } } - let raw_result: ResponseListSuccessDto> = - query_list_with_meta( - &self.state.surrealdb_ws, - &ResourceEnum::Roles.to_string(), - &meta, - conditions, - None, - "name", - None, - ) - .await?; + let raw_result: ResponseListSuccessDto> = query_list_with_meta( + &self.state.surrealdb_ws, + &ResourceEnum::Roles.to_string(), + &meta, + conditions, + None, + "name", + None, + None, + ) + .await?; let data = raw_result .data .into_iter() - .map(|role| RolesListItemDto { - id: extract_id(&role.id), - name: role.name, - created_at: role.created_at, - updated_at: role.updated_at, - permissions_count: role.permissions.len(), - }) + .map(|role| RolesSchema::list(&role)) .collect(); Ok(ResponseListSuccessDto { data, diff --git a/imphnen-iam/src/v1/roles/roles_schema.rs b/imphnen-iam/src/v1/roles/roles_schema.rs index 4898257..16e8ec9 100644 --- a/imphnen-iam/src/v1/roles/roles_schema.rs +++ b/imphnen-iam/src/v1/roles/roles_schema.rs @@ -1,5 +1,5 @@ use super::{ - RolesDetailItemDto, RolesDetailQueryDto, RolesRequestCreateDto, + RolesDetailItemDto, RolesDetailQueryDto, RolesListItemDto, RolesRequestCreateDto, RolesRequestUpdateDto, }; use crate::{ResourceEnum, make_thing}; @@ -112,4 +112,14 @@ impl RolesSchema { updated_at: Some(get_iso_date()), } } + + pub fn list(&self) -> RolesListItemDto { + RolesListItemDto { + id: self.id.id.to_raw(), + name: self.name.clone(), + permissions_count: self.permissions.len(), + created_at: self.created_at.clone(), + updated_at: self.updated_at.clone(), + } + } } diff --git a/imphnen-iam/src/v1/users/users_dto.rs b/imphnen-iam/src/v1/users/users_dto.rs index 4e3d8b0..3fd6a31 100644 --- a/imphnen-iam/src/v1/users/users_dto.rs +++ b/imphnen-iam/src/v1/users/users_dto.rs @@ -139,10 +139,10 @@ pub struct UsersListQueryDto { } impl UsersListQueryDto { - pub fn from(&self, role: String) -> UsersListItemDto { + pub fn from(&self) -> UsersListItemDto { UsersListItemDto { id: self.id.id.to_raw(), - role, + role: self.role.name.clone(), fullname: self.fullname.clone(), email: self.email.clone(), avatar: self.avatar.clone(), @@ -170,3 +170,23 @@ pub struct UsersDetailQueryDto { pub created_at: String, pub updated_at: String, } + +impl UsersDetailQueryDto { + pub fn from(&self) -> Self { + Self { + id: self.id.clone(), + role: RolesDetailQueryDto::from(self.role.clone()), + 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(), + is_deleted: self.is_deleted, + password: self.password.clone(), + birthdate: self.birthdate.clone(), + created_at: self.created_at.clone(), + updated_at: self.updated_at.clone(), + } + } +} diff --git a/imphnen-iam/src/v1/users/users_repository.rs b/imphnen-iam/src/v1/users/users_repository.rs index feab5df..d0efd33 100644 --- a/imphnen-iam/src/v1/users/users_repository.rs +++ b/imphnen-iam/src/v1/users/users_repository.rs @@ -1,8 +1,7 @@ use super::{UsersDetailQueryDto, UsersListItemDto, UsersListQueryDto, UsersSchema}; use crate::{ - AppState, MetaRequestDto, PermissionsQueryDto, ResourceEnum, - ResponseListSuccessDto, RolesDetailQueryDto, get_id, make_thing, - query_list_with_meta, + AppState, MetaRequestDto, ResourceEnum, ResponseListSuccessDto, get_id, + make_thing, query_list_with_meta, }; use anyhow::{Result, bail}; use imphnen_utils::DetailQueryBuilder; @@ -45,25 +44,15 @@ impl<'a> UsersRepository<'a> { vec!["is_deleted = false".into()], None, "fullname", - Some(vec![ - "email", - "fullname", - "id", - "is_active", - "is_deleted", - "role", - "role.permissions", - ]), + Some(vec!["*"]), + Some(vec!["role", "role.permissions"]), ) .await?; let data = raw_result .data .into_iter() - .map(|schema| { - let role = schema.clone().role.name; - schema.from(role) - }) + .map(|schema| UsersListQueryDto::from(&schema)) .collect::>(); Ok(ResponseListSuccessDto { @@ -81,21 +70,7 @@ impl<'a> UsersRepository<'a> { let builder = DetailQueryBuilder::new(ResourceEnum::Users.to_string()) .with_where("email") .where_value(email.clone()) - .with_select_fields(vec![ - "id", - "fullname", - "email", - "avatar", - "phone_number", - "is_active", - "is_deleted", - "gender", - "birthdate", - "password", - "created_at", - "updated_at", - "role", - ]) + .with_select_fields(vec!["*"]) .with_fetch("role") .with_fetch("role.permissions"); @@ -112,40 +87,7 @@ impl<'a> UsersRepository<'a> { bail!("User not found"); } - let permissions = user - .role - .permissions - .into_iter() - .map(|perm| PermissionsQueryDto { - 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, - }, - }) + Ok(UsersDetailQueryDto::from(&user)) } pub async fn query_user_by_id(&self, id: String) -> Result { @@ -153,21 +95,7 @@ impl<'a> UsersRepository<'a> { let builder = DetailQueryBuilder::new(ResourceEnum::Users.to_string()) .with_id(&id) - .with_select_fields(vec![ - "id", - "fullname", - "email", - "avatar", - "phone_number", - "is_active", - "is_deleted", - "gender", - "birthdate", - "password", - "created_at", - "updated_at", - "role", - ]) + .with_select_fields(vec!["*"]) .with_fetch("role") .with_fetch("role.permissions"); @@ -184,40 +112,7 @@ impl<'a> UsersRepository<'a> { bail!("User not found"); } - let permissions = user - .role - .permissions - .into_iter() - .map(|perm| PermissionsQueryDto { - 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, - }, - }) + Ok(UsersDetailQueryDto::from(&user)) } pub async fn query_create_user(&self, data: UsersSchema) -> Result { diff --git a/imphnen-utils/src/bind_filter.rs b/imphnen-utils/src/bind_filter.rs index c922765..0c3de18 100644 --- a/imphnen-utils/src/bind_filter.rs +++ b/imphnen-utils/src/bind_filter.rs @@ -4,11 +4,5 @@ pub fn bind_filter_value( query: Query<'_, Client>, val: String, ) -> Query<'_, Client> { - if let Ok(b) = val.parse::() { - query.bind(("filter", b)) - } else if let Ok(i) = val.parse::() { - query.bind(("filter", i)) - } else { - query.bind(("filter", val)) - } + query.bind(("filter", val)) // langsung string aja, udah cukup } diff --git a/imphnen-utils/src/crud.rs b/imphnen-utils/src/crud.rs deleted file mode 100644 index d95669b..0000000 --- a/imphnen-utils/src/crud.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub trait Crud { - 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") - } -} diff --git a/imphnen-utils/src/lib.rs b/imphnen-utils/src/lib.rs index 08ddcb6..a400ee9 100644 --- a/imphnen-utils/src/lib.rs +++ b/imphnen-utils/src/lib.rs @@ -2,7 +2,6 @@ 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; @@ -15,7 +14,6 @@ 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::*; diff --git a/imphnen-utils/src/query_builder.rs b/imphnen-utils/src/query_builder.rs index df6eab0..65e1d77 100644 --- a/imphnen-utils/src/query_builder.rs +++ b/imphnen-utils/src/query_builder.rs @@ -20,11 +20,13 @@ impl ListQueryBuilder { meta: &MetaRequestDto, search_field: impl Into, select_fields: Option>, + fetch_fields: Option>, ) -> 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_fetch(fetch_fields) .with_pagination(meta.page, meta.per_page); if let Some(fields) = select_fields { @@ -54,9 +56,10 @@ impl ListQueryBuilder { 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.conditions.push(format!( + "string::contains(string::lowercase({} ?? ''), string::lowercase($search))", + field + )); } } self @@ -65,7 +68,10 @@ impl ListQueryBuilder { 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.conditions.push(format!( + "string::contains(string::join('', [{}]), $filter)", + f + )); } } self @@ -89,8 +95,10 @@ impl ListQueryBuilder { self } - pub fn with_fetch(mut self, fetch: impl Into) -> Self { - self.fetch.push(fetch.into()); + pub fn with_fetch(mut self, fetches: Option>) -> Self { + if let Some(items) = fetches { + self.fetch.extend(items.into_iter().map(String::from)); + } self } diff --git a/imphnen-utils/src/query_list.rs b/imphnen-utils/src/query_list.rs index bb27db8..aa65645 100644 --- a/imphnen-utils/src/query_list.rs +++ b/imphnen-utils/src/query_list.rs @@ -12,6 +12,7 @@ pub async fn query_list_with_meta( custom_select: Option, search_field: &str, select_fields: Option>, + fetch_fields: Option>, ) -> Result>> where T: DeserializeOwned + Serialize, @@ -21,13 +22,20 @@ where let start = (page - 1) * per_page; let sql = custom_select.unwrap_or_else(|| { - ListQueryBuilder::from_meta(table, meta, search_field, select_fields).build() + ListQueryBuilder::from_meta( + table, + meta, + search_field, + select_fields, + fetch_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())); + query_exec = query_exec.bind(("search", search.to_lowercase().clone())); } } if let Some(filter_val) = &meta.filter {