diff --git a/.env b/.env index b326ce4..ad6c2e8 100644 --- a/.env +++ b/.env @@ -2,7 +2,7 @@ RUST_LOG=info PORT=7878 ALLOWED_ORIGIN="*" KAFKA_ENABLED="Y" -KAFKA_QUEUES="menu browser form process window menu_item menu_tree" +KAFKA_QUEUES="menu browser form process window menu_item menu_tree role" KAFKA_HOST="localhost:29092" KAFKA_GROUP="default" OPENSEARCH_URL="http://localhost:9200" diff --git a/src/bin/server.rs b/src/bin/server.rs index 515f170..91c456e 100644 --- a/src/bin/server.rs +++ b/src/bin/server.rs @@ -1,5 +1,5 @@ use std::env; -use dictionary_rs::{controller::{kafka::create_consumer, opensearch::{create, delete, IndexDocument}}, models::{browser::{browser_from_id, browsers, BrowserDocument}, form::{form_from_id, forms, FormDocument}, menu::{menu_from_id, menus, MenuDocument}, menu_item::MenuItemDocument, menu_tree::MenuTreeDocument, process::{process_from_id, processes, ProcessDocument}, window::{window_from_id, windows, WindowDocument}}}; +use dictionary_rs::{controller::{kafka::create_consumer, opensearch::{create, delete, IndexDocument}}, models::{browser::{browser_from_id, browsers, BrowserDocument}, form::{form_from_id, forms, FormDocument}, menu::{menu_from_id, menus, MenuDocument}, menu_item::MenuItemDocument, menu_tree::MenuTreeDocument, process::{process_from_id, processes, ProcessDocument}, role::RoleDocument, window::{window_from_id, windows, WindowDocument}}}; use dotenv::dotenv; use rdkafka::{Message, consumer::{CommitMode, Consumer}}; use salvo::{conn::tcp::TcpAcceptor, cors::Cors, http::header, hyper::Method, prelude::*}; @@ -505,6 +505,23 @@ async fn consume_queue() { Err(error) => log::warn!("{}", error) } } + } else if topic == "role" { + let _document = match serde_json::from_str(payload) { + Ok(value) => value, + Err(error) => { + log::warn!("{}", error); + RoleDocument { + document: None + } + }, + }; + if _document.document.is_some() { + let _menu_document: &dyn IndexDocument = &(_document.document.unwrap()); + match process_index(event_type, _menu_document).await { + Ok(_) => consumer.commit_message(&message, CommitMode::Async).unwrap(), + Err(error) => log::warn!("{}", error) + } + } } else if topic == "process" { let _document = match serde_json::from_str(payload) { Ok(value) => value, diff --git a/src/controller/opensearch.rs b/src/controller/opensearch.rs index ef3631d..ba1f561 100644 --- a/src/controller/opensearch.rs +++ b/src/controller/opensearch.rs @@ -268,6 +268,48 @@ pub async fn find(_document: &dyn IndexDocument, _search_value: String, _from: i Ok(list) } +pub async fn find_from_dsl_body(_document: &dyn IndexDocument, _body: serde_json::Value, _from: i64, _size: i64) -> Result, std::string::String> { + let client: OpenSearch = match create_opensearch_client() { + Ok(client_value) => client_value, + Err(error) => { + log::error!("{:?}", error); + return Err(error.to_string()); + } + }; + // Create + let _response: Result = client + .search(SearchParts::Index(&[&_document.index_name()])) + .from(_from) + .size(_size) + .body(_body) + .send() + .await + ; + let response = match _response { + Ok(value) => value, + Err(error) => { + log::error!("{:?}", error); + return Err(error.to_string()); + } + }; + if !response.status_code().is_success() { + return Err(format!("Error finding record {:?}", response.text().await)); + } + let response_body = match response.json::().await { + Ok(response) => response, + Err(error) => { + log::error!("{:?}", error); + return Err(error.to_string()); + }, + }; + let mut list: Vec:: = Vec::new(); + for hit in response_body["hits"]["hits"].as_array().unwrap() { + let value = hit["_source"].to_owned(); + list.push(value) + } + Ok(list) +} + pub async fn get_by_id(_document: &dyn IndexDocument) -> Result { let client = match create_opensearch_client() { Ok(client_value) => client_value, diff --git a/src/models/menu_item.rs b/src/models/menu_item.rs index a625442..c7e2f54 100644 --- a/src/models/menu_item.rs +++ b/src/models/menu_item.rs @@ -5,6 +5,8 @@ use std::{io::ErrorKind, io::Error}; use crate::{controller::opensearch::{IndexDocument, get_by_id, find, exists_index}, models::{user_index, role_index}}; +use super::role::Role; + #[derive(Deserialize, Extractible, Debug, Clone)] #[salvo(extract(default_source(from = "body")))] pub struct MenuItemDocument { @@ -98,6 +100,123 @@ impl MenuItem { menu.id = _id; menu } + + fn get_find_body_from_role(self: &Self, _role: Role) -> serde_json::Value { + // "W" Window + // "X" Form + // "S" Smart Browser + // "R" Report + // "P" Process + // "F" Workflow + let _window_access = match _role.to_owned().window_access { + Some(value) => value, + None => Vec::new() + }; + let _form_access = match _role.to_owned().form_access { + Some(value) => value, + None => Vec::new() + }; + let _browser_access = match _role.to_owned().browser_access { + Some(value) => value, + None => Vec::new() + }; + let _process_access = match _role.to_owned().process_access { + Some(value) => value, + None => Vec::new() + }; + let _workflow_access = match _role.to_owned().workflow_access { + Some(value) => value, + None => Vec::new() + }; + json!({ + "query": { + "bool": { + "should": [ + { + "bool": { + "must": [ + { + "terms": { + "action_id": _window_access + } + }, + { + "match": { + "action": "W" + } + } + ] + } + }, + { + "bool": { + "must": [ + { + "terms": { + "action_id": _form_access + } + }, + { + "match": { + "action": "X" + } + } + ] + } + }, + { + "bool": { + "must": [ + { + "terms": { + "action_id": _browser_access + } + }, + { + "match": { + "action": "S" + } + } + ] + } + }, + { + "bool": { + "must": [ + { + "terms": { + "action_id": _process_access + } + }, + { + "terms": { + "action": ["R", "P"] + } + } + ] + } + }, + { + "bool": { + "must": [ + { + "terms": { + "action_id": _workflow_access + } + }, + { + "match": { + "action": "F" + } + } + ] + } + } + ] + } + } + }) + } } impl IndexDocument for MenuItem { diff --git a/src/models/mod.rs b/src/models/mod.rs index cfa4326..b4d2ab5 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -6,6 +6,7 @@ pub mod window; pub mod generic; pub mod menu_item; pub mod menu_tree; +pub mod role; use serde::{Deserialize, Serialize}; use salvo::prelude::*; diff --git a/src/models/role.rs b/src/models/role.rs new file mode 100644 index 0000000..c1d6de7 --- /dev/null +++ b/src/models/role.rs @@ -0,0 +1,251 @@ +use serde::{Deserialize, Serialize}; +use salvo::prelude::*; +use serde_json::json; +use std::{io::ErrorKind, io::Error}; + +use crate::{controller::opensearch::{IndexDocument, get_by_id, find, exists_index}, models::{user_index, role_index}}; + +#[derive(Deserialize, Extractible, Debug, Clone)] +#[salvo(extract(default_source(from = "body")))] +pub struct RoleDocument { + pub document: Option +} + +#[derive(Serialize, Debug, Clone)] +pub struct RoleResponse { + pub role: Option +} + +#[derive(Serialize, Debug, Clone)] +pub struct RoleListResponse { + pub roles: Option> +} + +impl Default for RoleResponse { + fn default() -> Self { + RoleResponse { + role: None + } + } +} + +#[derive(Deserialize, Serialize, Extractible, Debug, Clone)] +pub struct Role { + pub id: Option, + pub uuid: Option, + pub name: Option, + pub description: Option, + pub tree_id: Option, + // index + pub index_value: Option, + pub language: Option, + pub client_id: Option, + pub role_id: Option, + pub user_id: Option, + // Access + pub window_access: Option>, + pub process_access: Option>, + pub form_access: Option>, + pub browser_access: Option>, + pub workflow_access: Option>, + pub dashboard_access: Option> +} + +impl Default for Role { + fn default() -> Self { + Self { + id: None, + uuid: None, + name: None, + description: None, + tree_id: None, + // index + index_value: None, + language: None, + client_id: None, + role_id: None, + user_id: None, + // Access + window_access: None, + process_access: None, + form_access: None, + browser_access: None, + workflow_access: None, + dashboard_access: None + } + } +} + +impl Role { + pub fn from_id(_id: Option) -> Self { + let mut menu = Role::default(); + menu.id = _id; + menu + } +} + +impl IndexDocument for Role { + fn mapping(self: &Self) -> serde_json::Value { + json!({ + "mappings" : { + "properties" : { + "uuid" : { "type" : "text" }, + "id" : { "type" : "integer" }, + "tree_id" : { "type" : "integer" }, + "name" : { "type" : "text" }, + "description" : { "type" : "text" } + } + } + }) + } + + fn data(self: &Self) -> serde_json::Value { + json!(self) + } + + fn id(self: &Self) -> String { + self.id.unwrap().to_string() + } + + fn index_name(self: &Self) -> String { + match &self.index_value { + Some(value) => value.to_string(), + None => "menu".to_string(), + } + } + + fn find(self: &Self, _search_value: String) -> serde_json::Value { + let mut query = "*".to_owned(); + query.push_str(&_search_value.to_owned()); + query.push_str(&"*".to_owned()); + + json!({ + "query": { + "query_string": { + "query": query + } + } + }) + } +} + +pub async fn role_from_id(_id: Option, _language: Option<&String>, _client_id: Option<&String>, _role_id: Option<&String>, _user_id: Option<&String>) -> Result { + if _id.is_none() || _id.map(|id| id <= 0).unwrap_or(false) { + return Err(Error::new(ErrorKind::InvalidData.into(), "Role Identifier is Mandatory").to_string()); + } + let mut _document = Role::from_id(_id); + + let _index_name = match get_index_name(_language, _client_id, _role_id, _user_id).await { + Ok(index_name) => index_name, + Err(error) => { + log::error!("Index name error: {:?}", error.to_string()); + return Err(error.to_string()) + } + }; + log::info!("Index to search {:}", _index_name); + + _document.index_value = Some(_index_name); + let _menu_document: &dyn IndexDocument = &_document; + match get_by_id(_menu_document).await { + Ok(value) => { + let menu: Role = serde_json::from_value(value).unwrap(); + log::info!("Finded Value: {:?}", menu.id); + Ok( + menu + ) + }, + Err(error) => { + log::error!("{}", error); + Err(error) + }, + } +} + +async fn get_index_name(_language: Option<&String>, _client_id: Option<&String>, _role_id: Option<&String>, _user_id: Option<&String>) -> Result { + // Validate + if _language.is_none() { + return Err(Error::new(ErrorKind::InvalidData.into(), "Language is Mandatory")); + } + if _client_id.is_none() { + return Err(Error::new(ErrorKind::InvalidData.into(), "Client is Mandatory")); + } + if _role_id.is_none() { + return Err(Error::new(ErrorKind::InvalidData.into(), "Role is Mandatory")); + } + + let _index: String = "menu".to_string(); + + let _user_index = user_index(_index.to_owned(), _language, _client_id, _role_id, _user_id); + let _role_index = role_index(_index.to_owned(), _language, _client_id, _role_id); + + // Find index + match exists_index(_user_index.to_owned()).await { + Ok(_) => { + log::info!("Find with user index `{:}`", _user_index); + Ok(_user_index) + }, + Err(_) => { + log::warn!("No user index `{:}`", _user_index); + match exists_index(_role_index.to_owned()).await { + Ok(_) => { + log::info!("Find with role index `{:}`", _role_index); + Ok(_role_index) + }, + Err(error) => { + log::error!("No role index `{:}`", _role_index); + return Err(Error::new(ErrorKind::InvalidData.into(), error)) + } + } + } + } +} + +pub async fn roles( + _language: Option<&String>, _client_id: Option<&String>, _role_id: Option<&String>, _user_id: Option<&String>, + _search_value: Option<&String>, _page_number: Option<&String>, _page_size: Option<&String> +) -> Result { + let _search_value = match _search_value { + Some(value) => value.clone(), + None => "".to_owned() + }; + + // Find index + let _index_name = match get_index_name(_language, _client_id, _role_id, _user_id).await { + Ok(index_name) => index_name, + Err(error) => { + log::error!("Index name error: {:?}", error.to_string()); + return Err(Error::new(ErrorKind::InvalidData.into(), error)) + } + }; + log::info!("Index to search {:}", _index_name); + + let mut _document = Role::default(); + _document.index_value = Some(_index_name); + let _menu_document: &dyn IndexDocument = &_document; + + // pagination + let page_number: i64 = match _page_number { + Some(value) => value.clone().parse::().to_owned(), + None => "0".parse::().to_owned() + }.unwrap(); + let page_size: i64 = match _page_size { + Some(value) => value.clone().parse::().to_owned(), + None => "100".parse::().to_owned() + }.unwrap(); + + match find(_menu_document, _search_value, page_number, page_size).await { + Ok(values) => { + let mut roles_list: Vec = vec![]; + for value in values { + let menu: Role = serde_json::from_value(value).unwrap(); + roles_list.push(menu.to_owned()); + } + Ok(RoleListResponse { + roles: Some(roles_list) + }) + }, + Err(error) => { + Err(Error::new(ErrorKind::InvalidData.into(), error)) + } + } +}