From 055ef6ac8e4f4ba34a8b811fffe724c537f7c290 Mon Sep 17 00:00:00 2001 From: Maulana Sodiqin Date: Tue, 20 May 2025 11:54:15 +0700 Subject: [PATCH] feat: adjust readme and add new seeder script --- README.md | 38 +++-- imphnen-backend/src/bin/seed_permissions.rs | 154 +++++------------- .../src/bin/seed_roles_permissions.rs | 67 ++++---- imphnen-backend/src/bin/seeder.rs | 25 +++ .../src/v1/permissions/permissions_enum.rs | 41 +++++ imphnen-iam/src/v1/users/users_dto.rs | 2 +- imphnen-iam/src/v1/users/users_repository.rs | 31 ++-- tests/src/iam/users/users_repository_test.rs | 23 +-- 8 files changed, 186 insertions(+), 195 deletions(-) create mode 100644 imphnen-backend/src/bin/seeder.rs diff --git a/README.md b/README.md index a7664e1..dae176c 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,14 @@ This repository serves as the **monorepo** for all backend services of IMPHNEN. It encompasses several main services: -1. **Core Service** - Provides fundamental functionalities and shared resources for other services. -2. **IAM Service** - Handles identity and access management across IMPHNEN applications. -3. **CMS Service** - Supports the cms services by IMPHNEN [Landing Page website](https://imphnen.dev/). -4. **Gacha Service** - Supports the gacha services by IMPHNEN [Gacha website](https://gacha.imphnen.dev/). -5. **Dimentorin Service** - Supports the mentoring services by IMPHNEN [Dimentorin website](https://dimentorin.imphnen.dev/). -6. **Gateway Service** - Acts as the API gateway, routing requests to appropriate services. +1. **IMPHNEN-Backend** - Provides fundamental functionalities and shared resources for other services. +2. **IMPHNEN-IAM** - Handles identity and access management across IMPHNEN applications. +3. **IMPHNEN-CMS** - Supports the cms services by IMPHNEN [Landing Page website](https://imphnen.dev/). +4. **IMPHNEN-Gacha** - Supports the gacha services by IMPHNEN [Gacha website](https://gacha.imphnen.dev/). +5. **IMPHNEN-Dimentorin** - Supports the mentoring services by IMPHNEN [Dimentorin website](https://dimentorin.imphnen.dev/). +6. **IMPHNEN-Gateway** - Acts as the API gateway, routing requests to appropriate services. -## How to Install +## IMPORTANT How to Install 1. **Clone the repository**: @@ -39,7 +39,7 @@ This repository serves as the **monorepo** for all backend services of IMPHNEN. if you use unix based system ```sh - sh apply-env.sh + source ./apply-env.sh ``` - Modify the `.env` files with your specific configuration settings. @@ -49,7 +49,15 @@ This repository serves as the **monorepo** for all backend services of IMPHNEN. Ensure you have [Rust](https://www.rust-lang.org/) installed. Then, run: ```sh - cargo build + cargo check + ``` + +4. **Run the seeders**: + + to run the seeders, run: + + ```sh + cargo run --bin seeder ``` ## How to Run @@ -64,10 +72,16 @@ To run the services in development mode: docker-compose up -d ``` -2. **Run the desired service**. For example, to run the Core Service: +2. **Run using cargo run**. For example, to run the Core Service: ```sh - cargo run -p imphnen-core-service --bin api + cargo run --bin api + ``` + +3. **Run using cargo watch**. For example, to run the Core Service: + + ```sh + cargo watch -x "run --bin api" ``` ### Production @@ -83,7 +97,7 @@ For production deployment: 2. **Run the Docker container**: ```sh - docker run -d --env-file .env -p 8080:8080 imphnen-backend-service + docker run -d --env-file .env -p 3000:3000 imphnen-backend-service ``` Adjust the port and environment variables as needed. diff --git a/imphnen-backend/src/bin/seed_permissions.rs b/imphnen-backend/src/bin/seed_permissions.rs index e412651..f303ca1 100644 --- a/imphnen-backend/src/bin/seed_permissions.rs +++ b/imphnen-backend/src/bin/seed_permissions.rs @@ -1,3 +1,4 @@ +use imphnen_iam::PermissionsEnum; use imphnen_utils::{get_iso_date, Env}; use serde_json::json; use std::error::Error; @@ -15,131 +16,50 @@ async fn main() -> Result<(), Box> { db.use_ns(env.surrealdb_namespace) .use_db(env.surrealdb_dbname) .await?; - let permissions = vec![ - ( - "023e2dfe-93c3-4008-94a8-b5dff403f73b", - "Create Users", - Some("2025-01-29T06:08:23.838311+00"), - Some("2025-01-29T06:08:23.838312+00"), - ), - ( - "0269ed71-0ae0-4c43-ad29-e3d861d8f9a0", - "Create Permissions", - Some("2025-01-29T05:11:01.265+00"), - Some("2025-01-29T05:11:01.265001+00"), - ), - ( - "299cb4d5-6556-4cc9-b6c1-32e6d31e0f9b", - "Update Permissions", - Some("2025-01-29T05:11:01.265+00"), - Some("2025-01-29T05:11:01.265001+00"), - ), - ( - "319ee593-ff0a-4f29-bbaf-9feb3174a3a2", - "Create Roles", - Some("2025-01-29T05:11:01.265+00"), - Some("2025-01-29T05:11:01.265001+00"), - ), - ( - "319ee593-ff0a-4f29-bbaf-9feb3174a3a6", - "Read Detail Users", - Some("2025-01-29T05:11:01.265+00"), - Some("2025-01-29T05:11:01.265001+00"), - ), - ( - "35b0d992-65c8-4b62-b030-e6e0320e4048", - "Delete Roles", - Some("2025-01-29T05:34:40.621554+00"), - Some("2025-01-29T05:34:40.621555+00"), - ), - ( - "4da8b434-89f9-4d91-85ae-eebd63cdbeda", - "Update Activate Users", - Some("2025-02-01T12:38:09.741726+00"), - Some("2025-02-01T12:38:09.741727+00"), - ), - ( - "73888d18-b3e9-4f62-95a5-ba2c0d69fccb", - "Read Detail Roles", - Some("2025-01-29T05:13:06.445925+00"), - Some("2025-01-29T10:31:46.408564+00"), - ), - ( - "7c15e31d-36e2-49f9-97db-138c03fb0cf6", - "Read List Users", - Some("2025-01-28T15:02:41.772931+00"), - Some("2025-01-28T15:02:41.772933+00"), - ), - ( - "7d4b1379-4960-416a-b045-98cd82c0cac9", - "Read Detail Sessions", - Some("2025-02-24T16:52:26.886664+00"), - Some("2025-02-24T16:52:26.886673+00"), - ), - ( - "8195eeb8-e64f-4172-aa57-596492c84a72", - "Read List Permissions", - Some("2025-01-28T15:05:28.6299+00"), - Some("2025-01-28T15:05:28.629901+00"), - ), - ( - "81eba91d-b8ab-44b9-bbfe-4e6da2f98952", - "Read List Tests", - Some("2025-02-24T16:52:27.179542+00"), - Some("2025-02-24T16:52:27.179551+00"), - ), - ( - "9164ca6e-c7e3-4238-a15f-f36ab9577e7e", - "Read List Roles", - Some("2025-01-29T05:34:40.621554+00"), - Some("2025-01-29T05:34:40.621555+00"), - ), - ( - "96df0689-2ae9-4894-bf00-837c19415e5c", - "Delete Users", - Some("2025-02-02T06:52:05.195565+00"), - Some("2025-02-02T06:52:05.195565+00"), - ), - ( - "98b3dc4c-0124-461f-afcd-166637c5e6e8", - "Update Users", - Some("2025-01-29T05:34:40.621554+00"), - Some("2025-01-29T05:34:40.621555+00"), - ), - ( - "a00d5608-4c48-4542-845c-dfe004687022", - "Update Roles", - Some("2025-01-29T05:34:40.621554+00"), - Some("2025-01-29T05:34:40.621555+00"), - ), - ( - "b2dc3928-86ba-4c59-a03d-0b57d5183ebc", - "Delete Permissions", - Some("2025-01-29T05:14:22.511084+00"), - Some("2025-01-29T05:14:22.511085+00"), - ), - ( - "dad435cf-042c-41bd-a946-cea61ed2ffbc", - "Read Detail Permissions", - Some("2025-01-28T15:07:10.990214+00"), - Some("2025-01-28T15:07:10.990214+00"), - ), - ]; - for (id, name, _created_at, _updated_at) in permissions { + + for permission in [ + PermissionsEnum::ReadListUsers, + PermissionsEnum::ReadDetailUsers, + PermissionsEnum::CreateUsers, + PermissionsEnum::DeleteUsers, + PermissionsEnum::UpdateUsers, + PermissionsEnum::ActivateUsers, + PermissionsEnum::ReadListRoles, + PermissionsEnum::ReadDetailRoles, + PermissionsEnum::CreateRoles, + PermissionsEnum::DeleteRoles, + PermissionsEnum::UpdateRoles, + PermissionsEnum::ReadListPermissions, + PermissionsEnum::ReadDetailPermissions, + PermissionsEnum::CreatePermissions, + PermissionsEnum::DeletePermissions, + PermissionsEnum::UpdatePermissions, + PermissionsEnum::CreateGachaClaims, + PermissionsEnum::ReadDetailGachaClaims, + PermissionsEnum::ReadListGachaItems, + PermissionsEnum::ReadDetailGachaItems, + PermissionsEnum::CreateGachaItems, + PermissionsEnum::DeleteGachaItems, + PermissionsEnum::UpdateGachaItems, + PermissionsEnum::ReadDetailGachaRolls, + PermissionsEnum::CreateGachaRolls, + PermissionsEnum::ExecuteGachaRolls, + ] { db.query("CREATE type::thing('app_permissions', $id) CONTENT $data") - .bind(("id", id)) + .bind(("id", permission.id())) .bind(( "data", json!({ - "name": name, - "is_deleted": false, - "created_at": get_iso_date(), - "updated_at": get_iso_date() + "name": permission.to_string(), + "is_deleted": false, + "created_at": get_iso_date(), + "updated_at": get_iso_date() }), )) .await?; - println!("✅ Inserted: {}", name); + println!("✅ Inserted: {}", permission.to_string()); } + println!("✅ All Permissions seeded"); Ok(()) } diff --git a/imphnen-backend/src/bin/seed_roles_permissions.rs b/imphnen-backend/src/bin/seed_roles_permissions.rs index 90f5484..882bd18 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_iam::{get_iso_date, make_thing, Env}; +use imphnen_iam::{get_iso_date, make_thing, Env, PermissionsEnum}; use std::error::Error; use surrealdb::{engine::remote::ws::Ws, opt::auth::Root, Surreal}; @@ -6,52 +6,51 @@ use surrealdb::{engine::remote::ws::Ws, opt::auth::Root, Surreal}; async fn main() -> Result<(), Box> { let env = Env::new(); let db = Surreal::new::(env.surrealdb_url).await?; - db.signin(Root { username: &env.surrealdb_username, password: &env.surrealdb_password, }) .await?; - db.use_ns(env.surrealdb_namespace) .use_db(env.surrealdb_dbname) .await?; - - 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", - ]; - + let permission_refs_admin: Vec<_> = [ + PermissionsEnum::ReadListUsers, + PermissionsEnum::ReadDetailUsers, + PermissionsEnum::CreateUsers, + PermissionsEnum::DeleteUsers, + PermissionsEnum::UpdateUsers, + PermissionsEnum::ActivateUsers, + PermissionsEnum::ReadListRoles, + PermissionsEnum::ReadDetailRoles, + PermissionsEnum::CreateRoles, + PermissionsEnum::DeleteRoles, + PermissionsEnum::UpdateRoles, + PermissionsEnum::ReadListPermissions, + PermissionsEnum::ReadDetailPermissions, + PermissionsEnum::CreatePermissions, + PermissionsEnum::DeletePermissions, + PermissionsEnum::UpdatePermissions, + PermissionsEnum::CreateGachaClaims, + PermissionsEnum::ReadDetailGachaClaims, + PermissionsEnum::ReadListGachaItems, + PermissionsEnum::ReadDetailGachaItems, + PermissionsEnum::CreateGachaItems, + PermissionsEnum::DeleteGachaItems, + PermissionsEnum::UpdateGachaItems, + PermissionsEnum::ReadDetailGachaRolls, + PermissionsEnum::CreateGachaRolls, + PermissionsEnum::ExecuteGachaRolls, + ] + .iter() + .map(|perm| make_thing("app_permissions", perm.id())) + .collect(); 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"); - + println!("✅ All permissions successfully added to Admin role"); Ok(()) } diff --git a/imphnen-backend/src/bin/seeder.rs b/imphnen-backend/src/bin/seeder.rs new file mode 100644 index 0000000..f82af9c --- /dev/null +++ b/imphnen-backend/src/bin/seeder.rs @@ -0,0 +1,25 @@ +use std::error::Error; +use std::process::Command; + +fn run_seed(bin: &str) -> Result<(), Box> { + println!("🔧 Seeding: {bin}"); + let status = Command::new("cargo").args(["run", "--bin", bin]).status()?; + + if !status.success() { + Err(format!("❌ Failed to run seed: {bin}").into()) + } else { + Ok(()) + } +} + +fn main() -> Result<(), Box> { + println!("🚀 Running all seeders...\n"); + + run_seed("seed_permissions")?; + run_seed("seed_roles")?; + run_seed("seed_roles_permissions")?; + run_seed("seed_users")?; + + println!("\n✅ All seeding completed successfully."); + Ok(()) +} diff --git a/imphnen-iam/src/v1/permissions/permissions_enum.rs b/imphnen-iam/src/v1/permissions/permissions_enum.rs index 0c6850c..c28d8a4 100644 --- a/imphnen-iam/src/v1/permissions/permissions_enum.rs +++ b/imphnen-iam/src/v1/permissions/permissions_enum.rs @@ -63,3 +63,44 @@ impl fmt::Display for PermissionsEnum { write!(f, "{}", permission_str) } } + +impl PermissionsEnum { + pub fn id(&self) -> &'static str { + match self { + PermissionsEnum::ReadListUsers => "7c15e31d-36e2-49f9-97db-138c03fb0cf6", + PermissionsEnum::ReadDetailUsers => "319ee593-ff0a-4f29-bbaf-9feb3174a3a6", + PermissionsEnum::CreateUsers => "023e2dfe-93c3-4008-94a8-b5dff403f73b", + PermissionsEnum::DeleteUsers => "96df0689-2ae9-4894-bf00-837c19415e5c", + PermissionsEnum::UpdateUsers => "98b3dc4c-0124-461f-afcd-166637c5e6e8", + PermissionsEnum::ActivateUsers => "4da8b434-89f9-4d91-85ae-eebd63cdbeda", + PermissionsEnum::ReadListRoles => "9164ca6e-c7e3-4238-a15f-f36ab9577e7e", + PermissionsEnum::ReadDetailRoles => "73888d18-b3e9-4f62-95a5-ba2c0d69fccb", + PermissionsEnum::CreateRoles => "319ee593-ff0a-4f29-bbaf-9feb3174a3a2", + PermissionsEnum::DeleteRoles => "35b0d992-65c8-4b62-b030-e6e0320e4048", + PermissionsEnum::UpdateRoles => "a00d5608-4c48-4542-845c-dfe004687022", + PermissionsEnum::ReadListPermissions => "8195eeb8-e64f-4172-aa57-596492c84a72", + PermissionsEnum::ReadDetailPermissions => { + "dad435cf-042c-41bd-a946-cea61ed2ffbc" + } + PermissionsEnum::CreatePermissions => "0269ed71-0ae0-4c43-ad29-e3d861d8f9a0", + PermissionsEnum::DeletePermissions => "b2dc3928-86ba-4c59-a03d-0b57d5183ebc", + PermissionsEnum::UpdatePermissions => "299cb4d5-6556-4cc9-b6c1-32e6d31e0f9b", + PermissionsEnum::CreateGachaClaims => "f41d53ce-4f88-4bb6-b9b4-5e3a8c38d962", + PermissionsEnum::ReadDetailGachaClaims => { + "c1c3d6c2-19fb-4b70-b58c-c19f2e8cfc79" + } + PermissionsEnum::ReadListGachaItems => "fa6eb842-0a61-40c2-9c24-b226ad975037", + PermissionsEnum::ReadDetailGachaItems => { + "9c7857d7-b5ae-4688-923d-ef5572e9bc8b" + } + PermissionsEnum::CreateGachaItems => "cf063be1-4d71-489e-b9fb-1c08c65f396c", + PermissionsEnum::DeleteGachaItems => "46f8c6cf-ea0c-4c90-860c-69e2e65f7eb1", + PermissionsEnum::UpdateGachaItems => "2d0cf4ae-56ae-4714-a12e-655cfc3d9eb2", + PermissionsEnum::ReadDetailGachaRolls => { + "53d6483a-04cd-4667-8792-2d0cc8e2d343" + } + PermissionsEnum::CreateGachaRolls => "18e36c63-fcb7-4877-b911-c5aa611e878f", + PermissionsEnum::ExecuteGachaRolls => "14c6a1cd-5c63-4643-89b5-b1a5f9920cc0", + } + } +} diff --git a/imphnen-iam/src/v1/users/users_dto.rs b/imphnen-iam/src/v1/users/users_dto.rs index 3fd6a31..98f8b55 100644 --- a/imphnen-iam/src/v1/users/users_dto.rs +++ b/imphnen-iam/src/v1/users/users_dto.rs @@ -139,7 +139,7 @@ pub struct UsersListQueryDto { } impl UsersListQueryDto { - pub fn from(&self) -> UsersListItemDto { + pub fn from(self) -> UsersListItemDto { UsersListItemDto { id: self.id.id.to_raw(), role: self.role.name.clone(), diff --git a/imphnen-iam/src/v1/users/users_repository.rs b/imphnen-iam/src/v1/users/users_repository.rs index d0efd33..d3ae20c 100644 --- a/imphnen-iam/src/v1/users/users_repository.rs +++ b/imphnen-iam/src/v1/users/users_repository.rs @@ -36,8 +36,7 @@ impl<'a> UsersRepository<'a> { meta: MetaRequestDto, ) -> Result>> { let db = &self.state.surrealdb_ws; - - let raw_result = query_list_with_meta::( + let result = query_list_with_meta::( db, &ResourceEnum::Users.to_string(), &meta, @@ -48,16 +47,14 @@ impl<'a> UsersRepository<'a> { Some(vec!["role", "role.permissions"]), ) .await?; - - let data = raw_result + let data = result .data .into_iter() - .map(|schema| UsersListQueryDto::from(&schema)) + .map(UsersListQueryDto::from) .collect::>(); - Ok(ResponseListSuccessDto { data, - meta: raw_result.meta, + meta: result.meta, }) } @@ -66,52 +63,46 @@ impl<'a> UsersRepository<'a> { email: String, ) -> Result { let db = &self.state.surrealdb_ws; - let builder = DetailQueryBuilder::new(ResourceEnum::Users.to_string()) .with_where("email") .where_value(email.clone()) .with_select_fields(vec!["*"]) .with_fetch("role") .with_fetch("role.permissions"); - 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"); }; - + if user.is_deleted { + bail!("User not found"); + } if user.role.is_deleted { bail!("User not found"); } - Ok(UsersDetailQueryDto::from(&user)) } pub async fn query_user_by_id(&self, id: String) -> Result { let db = &self.state.surrealdb_ws; - let builder = DetailQueryBuilder::new(ResourceEnum::Users.to_string()) .with_id(&id) .with_select_fields(vec!["*"]) .with_fetch("role") .with_fetch("role.permissions"); - let sql = builder.build(); - let result: Option = builder.apply_bindings(db.query(sql)).await?.take(0)?; - let Some(user) = result else { bail!("User not found"); }; - + if user.is_deleted { + bail!("User not found"); + } if user.role.is_deleted { bail!("User not found"); } - Ok(UsersDetailQueryDto::from(&user)) } @@ -156,7 +147,7 @@ impl<'a> UsersRepository<'a> { let db = &self.state.surrealdb_ws; let user = self.query_user_by_id(id).await?; if user.is_deleted { - bail!("User already deleted"); + bail!("User not found"); } let record_key = get_id(&user.id)?; let record: Option = db diff --git a/tests/src/iam/users/users_repository_test.rs b/tests/src/iam/users/users_repository_test.rs index ff1b9cd..4ab1849 100644 --- a/tests/src/iam/users/users_repository_test.rs +++ b/tests/src/iam/users/users_repository_test.rs @@ -1,31 +1,34 @@ +use surrealdb::Uuid; + 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) - .query_role_by_name("Student".into()) + .query_role_by_name("User".into()) .await .expect("Role not found") .id } +fn generate_unique_email(prefix: &str) -> String { + format!("{}_{}@example.com", prefix, Uuid::new_v4()) +} + #[tokio::test] async fn test_create_and_get_user() { let app_state = create_mock_app_state().await; let repo = UsersRepository::new(&app_state); - let user = create_test_user( - "testuser@example.com", - "Test User", - true, - &get_role_id(&app_state).await, - ); + let email = generate_unique_email("testuser"); + let user = + create_test_user(&email, "Test User", true, &get_role_id(&app_state).await); let create_result = repo.query_create_user(user.clone()).await; assert!(create_result.is_ok()); let fetched = repo .query_user_by_email("testuser@example.com".into()) .await; assert!(fetched.is_ok()); - assert_eq!(fetched.unwrap().email, "testuser@example.com"); + assert_eq!(fetched.unwrap().email, email.clone()); } #[tokio::test] @@ -100,9 +103,7 @@ async fn test_query_delete_user() { .query_delete_user(user_detail.id.id.to_raw().clone()) .await; assert!(delete_result.is_ok()); - let fetch_result = repo - .query_user_by_email(user_detail.id.id.to_raw().clone()) - .await; + let fetch_result = repo.query_user_by_email(user_detail.email.clone()).await; assert!(fetch_result.is_err()); }