Bug: feat(roles): add support for roles list



- Add support to list all roles for a user. Roles which are equal and below given entity level should be listed
- Add support to list roles at a given entity level for a user.
diff --git a/crates/api_models/src/events/user_role.rs b/crates/api_models/src/events/user_role.rs
index f88c318477a..81b38198cfc 100644
--- a/crates/api_models/src/events/user_role.rs
+++ b/crates/api_models/src/events/user_role.rs
@@ -2,8 +2,9 @@ use common_utils::events::{ApiEventMetric, ApiEventsType};
 
 use crate::user_role::{
     role::{
-        CreateRoleRequest, GetRoleRequest, ListRolesResponse, RoleInfoWithGroupsResponse,
-        RoleInfoWithPermissionsResponse, UpdateRoleRequest,
+        CreateRoleRequest, GetRoleRequest, ListRolesAtEntityLevelRequest, ListRolesResponse,
+        RoleInfoResponseNew, RoleInfoWithGroupsResponse, RoleInfoWithPermissionsResponse,
+        UpdateRoleRequest,
     },
     AcceptInvitationRequest, AuthorizationInfoResponse, DeleteUserRoleRequest,
     MerchantSelectRequest, UpdateUserRoleRequest,
@@ -22,6 +23,8 @@ common_utils::impl_api_event_type!(
         CreateRoleRequest,
         UpdateRoleRequest,
         ListRolesResponse,
+        ListRolesAtEntityLevelRequest,
+        RoleInfoResponseNew,
         RoleInfoWithGroupsResponse
     )
 );
diff --git a/crates/api_models/src/user.rs b/crates/api_models/src/user.rs
index abc09c8bbee..a0dadfea217 100644
--- a/crates/api_models/src/user.rs
+++ b/crates/api_models/src/user.rs
@@ -387,15 +387,3 @@ pub struct ListProfilesForUserInOrgAndMerchantAccountResponse {
     pub profile_id: id_type::ProfileId,
     pub profile_name: String,
 }
-
-#[derive(Debug, serde::Serialize)]
-pub struct ListUsersInEntityResponse {
-    pub email: pii::Email,
-    pub roles: Vec<MinimalRoleInfo>,
-}
-
-#[derive(Debug, serde::Serialize, Clone)]
-pub struct MinimalRoleInfo {
-    pub role_id: String,
-    pub role_name: String,
-}
diff --git a/crates/api_models/src/user_role.rs b/crates/api_models/src/user_role.rs
index ed6911016b1..4718da28d27 100644
--- a/crates/api_models/src/user_role.rs
+++ b/crates/api_models/src/user_role.rs
@@ -132,3 +132,9 @@ pub struct AcceptInvitationRequest {
 pub struct DeleteUserRoleRequest {
     pub email: pii::Email,
 }
+
+#[derive(Debug, serde::Serialize)]
+pub struct ListUsersInEntityResponse {
+    pub email: pii::Email,
+    pub roles: Vec<role::MinimalRoleInfo>,
+}
diff --git a/crates/api_models/src/user_role/role.rs b/crates/api_models/src/user_role/role.rs
index acbad9152ae..828dfeb20f8 100644
--- a/crates/api_models/src/user_role/role.rs
+++ b/crates/api_models/src/user_role/role.rs
@@ -8,7 +8,6 @@ pub struct CreateRoleRequest {
     pub role_name: String,
     pub groups: Vec<PermissionGroup>,
     pub role_scope: RoleScope,
-    pub entity_type: Option<EntityType>,
 }
 
 #[derive(Debug, serde::Deserialize, serde::Serialize)]
@@ -36,7 +35,33 @@ pub struct RoleInfoWithGroupsResponse {
     pub role_scope: RoleScope,
 }
 
+#[derive(Debug, serde::Serialize)]
+pub struct RoleInfoResponseNew {
+    pub role_id: String,
+    pub role_name: String,
+    pub entity_type: EntityType,
+    pub groups: Vec<PermissionGroup>,
+    pub scope: RoleScope,
+}
+
 #[derive(Debug, serde::Deserialize, serde::Serialize)]
 pub struct GetRoleRequest {
     pub role_id: String,
 }
+
+#[derive(Debug, serde::Deserialize, serde::Serialize)]
+pub struct ListRolesAtEntityLevelRequest {
+    pub entity_type: EntityType,
+}
+
+#[derive(Debug, serde::Deserialize, serde::Serialize)]
+pub enum RoleCheckType {
+    Invite,
+    Update,
+}
+
+#[derive(Debug, serde::Serialize, Clone)]
+pub struct MinimalRoleInfo {
+    pub role_id: String,
+    pub role_name: String,
+}
diff --git a/crates/diesel_models/src/query/role.rs b/crates/diesel_models/src/query/role.rs
index 7a0b1f8eb65..0a3c0e6d42f 100644
--- a/crates/diesel_models/src/query/role.rs
+++ b/crates/diesel_models/src/query/role.rs
@@ -1,8 +1,14 @@
+use async_bb8_diesel::AsyncRunQueryDsl;
 use common_utils::id_type;
-use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods};
+use diesel::{
+    associations::HasTable, debug_query, pg::Pg, result::Error as DieselError,
+    BoolExpressionMethods, ExpressionMethods, QueryDsl,
+};
+use error_stack::{report, ResultExt};
 
 use crate::{
-    enums::RoleScope, query::generics, role::*, schema::roles::dsl, PgPooledConn, StorageResult,
+    enums::RoleScope, errors, query::generics, role::*, schema::roles::dsl, PgPooledConn,
+    StorageResult,
 };
 
 impl RoleNew {
@@ -81,4 +87,49 @@ impl Role {
         )
         .await
     }
+
+    pub async fn generic_roles_list_for_org(
+        conn: &PgPooledConn,
+        org_id: id_type::OrganizationId,
+        merchant_id: Option<id_type::MerchantId>,
+        entity_type: Option<common_enums::EntityType>,
+        limit: Option<u32>,
+    ) -> StorageResult<Vec<Self>> {
+        let mut query = <Self as HasTable>::table()
+            .filter(dsl::org_id.eq(org_id))
+            .into_boxed();
+
+        if let Some(merchant_id) = merchant_id {
+            query = query.filter(
+                dsl::merchant_id
+                    .eq(merchant_id)
+                    .or(dsl::scope.eq(RoleScope::Organization)),
+            );
+        }
+
+        if let Some(entity_type) = entity_type {
+            query = query.filter(dsl::entity_type.eq(entity_type))
+        }
+
+        if let Some(limit) = limit {
+            query = query.limit(limit.into());
+        }
+
+        router_env::logger::debug!(query = %debug_query::<Pg,_>(&query).to_string());
+
+        match generics::db_metrics::track_database_call::<Self, _, _>(
+            query.get_results_async(conn),
+            generics::db_metrics::DatabaseOperation::Filter,
+        )
+        .await
+        {
+            Ok(value) => Ok(value),
+            Err(err) => match err {
+                DieselError::NotFound => {
+                    Err(report!(err)).change_context(errors::DatabaseError::NotFound)
+                }
+                _ => Err(report!(err)).change_context(errors::DatabaseError::Others),
+            },
+        }
+    }
 }
diff --git a/crates/router/src/core/user_role.rs b/crates/router/src/core/user_role.rs
index 4204e91678d..7752ce06e9a 100644
--- a/crates/router/src/core/user_role.rs
+++ b/crates/router/src/core/user_role.rs
@@ -559,7 +559,7 @@ pub async fn delete_user_role(
 pub async fn list_users_in_lineage(
     state: SessionState,
     user_from_token: auth::UserFromToken,
-) -> UserResponse<Vec<user_api::ListUsersInEntityResponse>> {
+) -> UserResponse<Vec<user_role_api::ListUsersInEntityResponse>> {
     let requestor_role_info = roles::RoleInfo::from_role_id(
         &state,
         &user_from_token.role_id,
@@ -644,7 +644,7 @@ pub async fn list_users_in_lineage(
             .map(|role_info| {
                 (
                     user_role.role_id.clone(),
-                    user_api::MinimalRoleInfo {
+                    user_role_api::role::MinimalRoleInfo {
                         role_id: user_role.role_id.clone(),
                         role_name: role_info.get_role_name().to_string(),
                     },
@@ -669,7 +669,7 @@ pub async fn list_users_in_lineage(
         user_role_map
             .into_iter()
             .map(|(user_id, role_id_vec)| {
-                Ok::<_, error_stack::Report<UserErrors>>(user_api::ListUsersInEntityResponse {
+                Ok::<_, error_stack::Report<UserErrors>>(user_role_api::ListUsersInEntityResponse {
                     email: email_map
                         .remove(&user_id)
                         .ok_or(UserErrors::InternalServerError)?,
diff --git a/crates/router/src/core/user_role/role.rs b/crates/router/src/core/user_role/role.rs
index 7a2c7020512..1a81c49d5a9 100644
--- a/crates/router/src/core/user_role/role.rs
+++ b/crates/router/src/core/user_role/role.rs
@@ -1,5 +1,5 @@
 use api_models::user_role::role::{self as role_api};
-use common_enums::RoleScope;
+use common_enums::{EntityType, RoleScope};
 use common_utils::generate_id_with_default_len;
 use diesel_models::role::{RoleNew, RoleUpdate};
 use error_stack::{report, ResultExt};
@@ -64,7 +64,7 @@ pub async fn create_role(
             org_id: user_from_token.org_id,
             groups: req.groups,
             scope: req.role_scope,
-            entity_type: req.entity_type,
+            entity_type: Some(EntityType::Merchant),
             created_by: user_from_token.user_id.clone(),
             last_modified_by: user_from_token.user_id,
             created_at: now,
@@ -213,3 +213,148 @@ pub async fn update_role(
         },
     ))
 }
+
+pub async fn list_roles_with_info(
+    state: SessionState,
+    user_from_token: UserFromToken,
+) -> UserResponse<Vec<role_api::RoleInfoResponseNew>> {
+    let user_role_info = user_from_token
+        .get_role_info_from_db(&state)
+        .await
+        .attach_printable("Invalid role_id in JWT")?;
+
+    let mut role_info_vec = PREDEFINED_ROLES
+        .iter()
+        .map(|(_, role_info)| role_info.clone())
+        .collect::<Vec<_>>();
+
+    let user_role_entity = user_role_info.get_entity_type();
+    let custom_roles = match user_role_entity {
+        EntityType::Organization => state
+            .store
+            .list_roles_for_org_by_parameters(&user_from_token.org_id, None, None, None)
+            .await
+            .change_context(UserErrors::InternalServerError)
+            .attach_printable("Failed to get roles")?,
+        EntityType::Merchant => state
+            .store
+            .list_roles_for_org_by_parameters(
+                &user_from_token.org_id,
+                Some(&user_from_token.merchant_id),
+                None,
+                None,
+            )
+            .await
+            .change_context(UserErrors::InternalServerError)
+            .attach_printable("Failed to get roles")?,
+        // TODO: Populate this from Db function when support for profile id and profile level custom roles is added
+        EntityType::Profile => Vec::new(),
+        EntityType::Internal => {
+            return Err(UserErrors::InvalidRoleOperationWithMessage(
+                "Internal roles are not allowed for this operation".to_string(),
+            )
+            .into());
+        }
+    };
+
+    role_info_vec.extend(custom_roles.into_iter().map(roles::RoleInfo::from));
+    let list_role_info_response = role_info_vec
+        .into_iter()
+        .filter_map(|role_info| {
+            if user_role_entity >= role_info.get_entity_type() {
+                Some(role_api::RoleInfoResponseNew {
+                    role_id: role_info.get_role_id().to_string(),
+                    role_name: role_info.get_role_name().to_string(),
+                    groups: role_info.get_permission_groups().to_vec(),
+                    entity_type: role_info.get_entity_type(),
+                    scope: role_info.get_scope(),
+                })
+            } else {
+                None
+            }
+        })
+        .collect::<Vec<_>>();
+
+    Ok(ApplicationResponse::Json(list_role_info_response))
+}
+
+pub async fn list_roles_at_entity_level(
+    state: SessionState,
+    user_from_token: UserFromToken,
+    req: role_api::ListRolesAtEntityLevelRequest,
+    check_type: role_api::RoleCheckType,
+) -> UserResponse<Vec<role_api::MinimalRoleInfo>> {
+    let user_entity_type = user_from_token
+        .get_role_info_from_db(&state)
+        .await
+        .attach_printable("Invalid role_id in JWT")?
+        .get_entity_type();
+
+    if req.entity_type > user_entity_type {
+        return Err(UserErrors::InvalidRoleOperationWithMessage(
+            "User is attempting to request list roles above the current entity level".to_string(),
+        )
+        .into());
+    }
+    let mut role_info_vec = PREDEFINED_ROLES
+        .iter()
+        .map(|(_, role_info)| role_info.clone())
+        .collect::<Vec<_>>();
+
+    let custom_roles = match req.entity_type {
+        EntityType::Organization => state
+            .store
+            .list_roles_for_org_by_parameters(
+                &user_from_token.org_id,
+                None,
+                Some(req.entity_type),
+                None,
+            )
+            .await
+            .change_context(UserErrors::InternalServerError)
+            .attach_printable("Failed to get roles")?,
+
+        EntityType::Merchant => state
+            .store
+            .list_roles_for_org_by_parameters(
+                &user_from_token.org_id,
+                Some(&user_from_token.merchant_id),
+                Some(req.entity_type),
+                None,
+            )
+            .await
+            .change_context(UserErrors::InternalServerError)
+            .attach_printable("Failed to get roles")?,
+        // TODO: Populate this from Db function when support for profile id and profile level custom roles is added
+        EntityType::Profile => Vec::new(),
+
+        EntityType::Internal => {
+            return Err(UserErrors::InvalidRoleOperationWithMessage(
+                "Internal roles are not allowed for this operation".to_string(),
+            )
+            .into());
+        }
+    };
+
+    role_info_vec.extend(custom_roles.into_iter().map(roles::RoleInfo::from));
+
+    let list_minimal_role_info = role_info_vec
+        .into_iter()
+        .filter_map(|role_info| {
+            let check_type = match check_type {
+                role_api::RoleCheckType::Invite => role_info.is_invitable(),
+                role_api::RoleCheckType::Update => role_info.is_updatable(),
+            };
+            if check_type && role_info.get_entity_type() == req.entity_type {
+                Some(role_api::MinimalRoleInfo {
+                    role_id: role_info.get_role_id().to_string(),
+                    role_name: role_info.get_role_name().to_string(),
+                })
+            } else {
+                None
+            }
+        })
+        .collect::<Vec<_>>();
+
+    Ok(ApplicationResponse::Json(list_minimal_role_info))
+}
diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs
index 316e8106c9d..fd8f387f001 100644
--- a/crates/router/src/db/kafka_store.rs
+++ b/crates/router/src/db/kafka_store.rs
@@ -3272,6 +3272,18 @@ impl RoleInterface for KafkaStore {
     ) -> CustomResult<Vec<storage::Role>, errors::StorageError> {
         self.diesel_store.list_all_roles(merchant_id, org_id).await
     }
+
+    async fn list_roles_for_org_by_parameters(
+        &self,
+        org_id: &id_type::OrganizationId,
+        merchant_id: Option<&id_type::MerchantId>,
+        entity_type: Option<enums::EntityType>,
+        limit: Option<u32>,
+    ) -> CustomResult<Vec<storage::Role>, errors::StorageError> {
+        self.diesel_store
+            .list_roles_for_org_by_parameters(org_id, merchant_id, entity_type, limit)
+            .await
+    }
 }
 
 #[async_trait::async_trait]
diff --git a/crates/router/src/db/role.rs b/crates/router/src/db/role.rs
index 5802142b166..ddcb24dc254 100644
--- a/crates/router/src/db/role.rs
+++ b/crates/router/src/db/role.rs
@@ -46,6 +46,14 @@ pub trait RoleInterface {
         merchant_id: &id_type::MerchantId,
         org_id: &id_type::OrganizationId,
     ) -> CustomResult<Vec<storage::Role>, errors::StorageError>;
+
+    async fn list_roles_for_org_by_parameters(
+        &self,
+        org_id: &id_type::OrganizationId,
+        merchant_id: Option<&id_type::MerchantId>,
+        entity_type: Option<enums::EntityType>,
+        limit: Option<u32>,
+    ) -> CustomResult<Vec<storage::Role>, errors::StorageError>;
 }
 
 #[async_trait::async_trait]
@@ -119,6 +127,26 @@ impl RoleInterface for Store {
             .await
             .map_err(|error| report!(errors::StorageError::from(error)))
     }
+
+    #[instrument(skip_all)]
+    async fn list_roles_for_org_by_parameters(
+        &self,
+        org_id: &id_type::OrganizationId,
+        merchant_id: Option<&id_type::MerchantId>,
+        entity_type: Option<enums::EntityType>,
+        limit: Option<u32>,
+    ) -> CustomResult<Vec<storage::Role>, errors::StorageError> {
+        let conn = connection::pg_connection_write(self).await?;
+        storage::Role::generic_roles_list_for_org(
+            &conn,
+            org_id.to_owned(),
+            merchant_id.cloned(),
+            entity_type,
+            limit,
+        )
+        .await
+        .map_err(|error| report!(errors::StorageError::from(error)))
+    }
 }
 
 #[async_trait::async_trait]
@@ -271,4 +299,31 @@ impl RoleInterface for MockDb {
 
         Ok(roles_list)
     }
+
+    #[instrument(skip_all)]
+    async fn list_roles_for_org_by_parameters(
+        &self,
+        org_id: &id_type::OrganizationId,
+        merchant_id: Option<&id_type::MerchantId>,
+        entity_type: Option<enums::EntityType>,
+        limit: Option<u32>,
+    ) -> CustomResult<Vec<storage::Role>, errors::StorageError> {
+        let roles = self.roles.lock().await;
+        let limit_usize = limit.unwrap_or(u32::MAX).try_into().unwrap_or(usize::MAX);
+        let roles_list: Vec<_> = roles
+            .iter()
+            .filter(|role| {
+                let matches_merchant = match merchant_id {
+                    Some(merchant_id) => role.merchant_id == *merchant_id,
+                    None => true,
+                };
+
+                matches_merchant && role.org_id == *org_id && role.entity_type == entity_type
+            })
+            .take(limit_usize)
+            .cloned()
+            .collect();
+
+        Ok(roles_list)
+    }
 }
diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs
index 42f46e39d2e..9b35e3ca475 100644
--- a/crates/router/src/routes/app.rs
+++ b/crates/router/src/routes/app.rs
@@ -1842,7 +1842,19 @@ impl User {
                         .route(web::get().to(get_role_from_token))
                         .route(web::post().to(create_role)),
                 )
-                .service(web::resource("/list").route(web::get().to(list_all_roles)))
+                .service(web::resource("/v2/list").route(web::get().to(list_roles_with_info)))
+                .service(
+                    web::scope("/list")
+                        .service(web::resource("").route(web::get().to(list_all_roles)))
+                        .service(
+                            web::resource("/invite")
+                                .route(web::get().to(list_invitable_roles_at_entity_level)),
+                        )
+                        .service(
+                            web::resource("/update")
+                                .route(web::get().to(list_updatable_roles_at_entity_level)),
+                        ),
+                )
                 .service(
                     web::resource("/{role_id}")
                         .route(web::get().to(get_role))
diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs
index f2a1d7e3458..8f61d78c4a1 100644
--- a/crates/router/src/routes/lock_utils.rs
+++ b/crates/router/src/routes/lock_utils.rs
@@ -253,6 +253,9 @@ impl From<Flow> for ApiIdentifier {
             | Flow::AuthSelect => Self::User,
 
             Flow::ListRoles
+            | Flow::ListRolesV2
+            | Flow::ListInvitableRolesAtEntityLevel
+            | Flow::ListUpdatableRolesAtEntityLevel
             | Flow::GetRole
             | Flow::GetRoleFromToken
             | Flow::UpdateUserRole
diff --git a/crates/router/src/routes/user_role.rs b/crates/router/src/routes/user_role.rs
index 5f1e4301d45..539409ab75f 100644
--- a/crates/router/src/routes/user_role.rs
+++ b/crates/router/src/routes/user_role.rs
@@ -243,3 +243,70 @@ pub async fn list_users_in_lineage(state: web::Data<AppState>, req: HttpRequest)
     ))
     .await
 }
+
+pub async fn list_roles_with_info(state: web::Data<AppState>, req: HttpRequest) -> HttpResponse {
+    let flow = Flow::ListRolesV2;
+
+    Box::pin(api::server_wrap(
+        flow,
+        state.clone(),
+        &req,
+        (),
+        |state, user_from_token, _, _| role_core::list_roles_with_info(state, user_from_token),
+        &auth::JWTAuth(Permission::UsersRead),
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
+
+pub async fn list_invitable_roles_at_entity_level(
+    state: web::Data<AppState>,
+    req: HttpRequest,
+    query: web::Query<role_api::ListRolesAtEntityLevelRequest>,
+) -> HttpResponse {
+    let flow = Flow::ListInvitableRolesAtEntityLevel;
+
+    Box::pin(api::server_wrap(
+        flow,
+        state.clone(),
+        &req,
+        query.into_inner(),
+        |state, user_from_token, req, _| {
+            role_core::list_roles_at_entity_level(
+                state,
+                user_from_token,
+                req,
+                role_api::RoleCheckType::Invite,
+            )
+        },
+        &auth::JWTAuth(Permission::UsersRead),
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
+
+pub async fn list_updatable_roles_at_entity_level(
+    state: web::Data<AppState>,
+    req: HttpRequest,
+    query: web::Query<role_api::ListRolesAtEntityLevelRequest>,
+) -> HttpResponse {
+    let flow = Flow::ListUpdatableRolesAtEntityLevel;
+
+    Box::pin(api::server_wrap(
+        flow,
+        state.clone(),
+        &req,
+        query.into_inner(),
+        |state, user_from_token, req, _| {
+            role_core::list_roles_at_entity_level(
+                state,
+                user_from_token,
+                req,
+                role_api::RoleCheckType::Update,
+            )
+        },
+        &auth::JWTAuth(Permission::UsersRead),
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs
index 538ae888cd5..51bb4b64730 100644
--- a/crates/router_env/src/logger/types.rs
+++ b/crates/router_env/src/logger/types.rs
@@ -366,6 +366,12 @@ pub enum Flow {
     GetRolesInfo,
     /// List roles
     ListRoles,
+    /// List roles v2
+    ListRolesV2,
+    /// List invitable roles at entity level
+    ListInvitableRolesAtEntityLevel,
+    /// List updatable roles at entity level
+    ListUpdatableRolesAtEntityLevel,
     /// Get role
     GetRole,
     /// Get role from token

## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [x] New feature
- [ ] Enhancement
- [ ] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
Add support to 
- List available roles with info for the user
- List roles at entity level for the user

### Additional Changes

- [ ] This PR modifies the API contract
- [ ] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
Closes #5753 


## How did you test it?

To List roles available roles in hierarchy with info:
```
curl --location 'http://localhost:8080/user/role/v2/list' \
--header 'Authorization: Bearer JWT'
```
Response
```
[
    {
        "role_id": "merchant_operator",
        "role_name": "operator",
        "entity_type": "merchant",
        "groups": [
            "operations_view",
            "operations_manage",
            "connectors_view",
            "workflows_view",
            "analytics_view",
            "users_view",
            "merchant_details_view"
        ],
        "scope": "organization"
    },
    {
        "role_id": "merchant_developer",
        "role_name": "developer",
        "entity_type": "merchant",
        "groups": [
            "operations_view",
            "connectors_view",
            "analytics_view",
            "users_view",
            "merchant_details_view",
            "merchant_details_manage"
        ],
        "scope": "organization"
    },
    {
        "role_id": "merchant_iam_admin",
        "role_name": "iam",
        "entity_type": "merchant",
        "groups": [
            "operations_view",
            "analytics_view",
            "users_view",
            "users_manage",
            "merchant_details_view"
        ],
        "scope": "organization"
    },
    {
        "role_id": "merchant_view_only",
        "role_name": "view_only",
        "entity_type": "merchant",
        "groups": [
            "operations_view",
            "connectors_view",
            "workflows_view",
            "analytics_view",
            "users_view",
            "merchant_details_view"
        ],
        "scope": "organization"
    },
    {
        "role_id": "merchant_customer_support",
        "role_name": "customer_support",
        "entity_type": "merchant",
        "groups": [
            "operations_view",
            "analytics_view",
            "users_view",
            "merchant_details_view"
        ],
        "scope": "organization"
    },
    {
        "role_id": "merchant_admin",
        "role_name": "admin",
        "entity_type": "merchant",
        "groups": [
            "operations_view",
            "operations_manage",
            "connectors_view",
            "connectors_manage",
            "workflows_view",
            "workflows_manage",
            "analytics_view",
            "users_view",
            "users_manage",
            "merchant_details_view",
            "merchant_details_manage"
        ],
        "scope": "organization"
    },
    {
        "role_id": "org_admin",
        "role_name": "organization_admin",
        "entity_type": "organization",
        "groups": [
            "operations_view",
            "operations_manage",
            "connectors_view",
            "connectors_manage",
            "workflows_view",
            "workflows_manage",
            "analytics_view",
            "users_view",
            "users_manage",
            "merchant_details_view",
            "merchant_details_manage",
            "organization_manage"
        ],
        "scope": "organization"
    }
]
```
To list roles at entity level for invite:
```
curl --location 'http://localhost:8080/user/role/list/invite?entity_type=merchant' \
--header 'Authorization: Bearer JWT'
```
Entity type can be `organization`, `merchant` or `profile`
Response:
```
[
    {
        "role_id": "merchant_customer_support",
        "role_name": "customer_support"
    },
    {
        "role_id": "merchant_admin",
        "role_name": "admin"
    },
    {
        "role_id": "merchant_view_only",
        "role_name": "view_only"
    },
    {
        "role_id": "merchant_developer",
        "role_name": "developer"
    },
    {
        "role_id": "merchant_iam_admin",
        "role_name": "iam"
    },
    {
        "role_id": "merchant_operator",
        "role_name": "operator"
    }
]
```
To list all roles at entity level for update
```
curl --location 'http://localhost:8080/user/role/list/update?entity_type=merchant' \
--header 'Authorization: Bearer JWT'
```
Response:
```
[
    {
        "role_id": "merchant_customer_support",
        "role_name": "customer_support"
    },
    {
        "role_id": "merchant_admin",
        "role_name": "admin"
    },
    {
        "role_id": "merchant_view_only",
        "role_name": "view_only"
    },
    {
        "role_id": "merchant_developer",
        "role_name": "developer"
    },
    {
        "role_id": "merchant_iam_admin",
        "role_name": "iam"
    },
    {
        "role_id": "merchant_operator",
        "role_name": "operator"
    }
]
```
## Checklist
<!-- Put an `x` in the boxes that apply -->

- [x] I formatted the code `cargo +nightly fmt --all`
- [x] I addressed lints thrown by `cargo clippy`
- [x] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
