diff --git a/.gitignore b/.gitignore index ad592ec..963ab03 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /.vscode /target -/docker \ No newline at end of file +/docker +/assets \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 0a54d86..bb2eef7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,6 +30,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -45,6 +54,18 @@ dependencies = [ "libc", ] +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", +] + [[package]] name = "async-trait" version = "0.1.80" @@ -144,6 +165,21 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64-compat" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a8d4d2746f89841e49230dd26917df1876050f95abafafbe34f47cb534b88d7" +dependencies = [ + "byteorder", +] + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bitflags" version = "1.3.2" @@ -168,6 +204,15 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -204,6 +249,12 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.6.0" @@ -768,6 +819,21 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jwt" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f" +dependencies = [ + "base64 0.13.1", + "crypto-common", + "digest", + "hmac", + "serde", + "serde_json", + "sha2", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -982,6 +1048,17 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + [[package]] name = "pbkdf2" version = "0.11.0" @@ -1110,6 +1187,35 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "regex" +version = "1.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" + [[package]] name = "resolv-conf" version = "0.7.0" @@ -1141,12 +1247,17 @@ version = "0.1.0" dependencies = [ "async-trait", "axum", + "base64-compat", "cargo_metadata", "crossterm", + "hmac", + "jwt", "lazy_static", + "regex", "rust_db_manager_core", "serde", "serde_json", + "sha2", "tokio", "tower-http", "uuid", @@ -1155,8 +1266,9 @@ dependencies = [ [[package]] name = "rust_db_manager_core" version = "0.1.1" -source = "git+https://github.com/Rafael24595/rust-db-manager.git?branch=dev#403dba2c663bac7272a28f4d23aa2ad5f50e84cb" +source = "git+https://github.com/Rafael24595/rust-db-manager.git?branch=dev#45053f5f44d616b1c25e9a9a6f0face43fd7a4d9" dependencies = [ + "argon2", "async-trait", "cargo_metadata", "crossterm", @@ -1164,6 +1276,7 @@ dependencies = [ "lazy_static", "mongodb", "serde_json", + "strum", "tokio", "uuid", ] @@ -1480,6 +1593,28 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strum" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.58", +] + [[package]] name = "subtle" version = "2.5.0" diff --git a/Cargo.toml b/Cargo.toml index 8f0e0a5..ad1b64c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,12 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[profile.dev] +opt-level = 3 + +[profile.release] +opt-level = 3 + [dependencies] tokio = { version = "1", features = ["full"] } lazy_static = "1.4.0" @@ -16,4 +22,9 @@ serde_json = "1.0" crossterm = "0.27.0" uuid = "1.8.0" cargo_metadata = "0.18.1" +base64-compat = "1.0.0" +hmac = "0.12.1" +sha2 = "0.10.8" +jwt = "0.16.0" +regex = "1.10.4" rust_db_manager_core = { git = "https://github.com/Rafael24595/rust-db-manager.git", branch = "dev" } \ No newline at end of file diff --git a/src/commons/configuration/web_configuration.rs b/src/commons/configuration/web_configuration.rs index 2a86f9f..a0c0f57 100644 --- a/src/commons/configuration/web_configuration.rs +++ b/src/commons/configuration/web_configuration.rs @@ -17,6 +17,8 @@ pub struct WebConfiguration { } impl WebConfiguration { + + pub const COOKIE_NAME: &'static str = "DB_TOKEN"; pub fn initialize() -> WebConfiguration { let _ = Configuration::initialize(); diff --git a/src/commons/exception/api_exception.rs b/src/commons/exception/api_exception.rs index 64dbf18..00df042 100644 --- a/src/commons/exception/api_exception.rs +++ b/src/commons/exception/api_exception.rs @@ -3,8 +3,6 @@ use std::error::Error; use rust_db_manager_core::commons::exception::connect_exception::ConnectException; -pub(crate) const EXCEPTION_HEADER: &str = "Error-Code"; - #[derive(Debug, Clone)] pub struct ApiException { status: u16, diff --git a/src/commons/exception/auth_exception.rs b/src/commons/exception/auth_exception.rs new file mode 100644 index 0000000..c2db963 --- /dev/null +++ b/src/commons/exception/auth_exception.rs @@ -0,0 +1,60 @@ +use std::fmt; +use std::error::Error; + +use super::api_exception::ApiException; + +#[derive(Debug, Clone)] +pub struct AuthException { + status: u16, + message: String, + reset: bool, +} + +impl fmt::Display for AuthException { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "AuthException: {}", self.message) + } +} + +impl Error for AuthException {} + + +impl AuthException { + + pub fn from(status: u16, exception: ApiException, reset: bool) -> AuthException { + AuthException { + status: status, + message: exception.message(), + reset: reset + } + } + + pub fn new(status: u16, message: String) -> AuthException { + AuthException { + status, + message, + reset: false + } + } + + pub fn new_reset(status: u16, message: String) -> AuthException { + AuthException { + status, + message, + reset: true + } + } + + pub fn status(&self) -> u16 { + return self.status; + } + + pub fn message(&self) -> String { + return self.message.clone(); + } + + pub fn reset(&self) -> bool { + return self.reset; + } + +} \ No newline at end of file diff --git a/src/domain/builder_db_connection_data.rs b/src/domain/builder_db_connection_data.rs index 350d14b..b71958f 100644 --- a/src/domain/builder_db_connection_data.rs +++ b/src/domain/builder_db_connection_data.rs @@ -1,14 +1,14 @@ use rust_db_manager_core::{domain::connection_data::ConnectionData, infrastructure::repository::e_db_repository::EDBRepository}; -use crate::{commons::exception::api_exception::ApiException, infrastructure::dto::db_service::dto_db_connection_data::DTOConnectionData}; +use crate::{commons::exception::api_exception::ApiException, infrastructure::dto::service::generate::dto_db_connection_data::DTODBConnectionData}; pub struct BuilderConnectionData { } impl BuilderConnectionData { - pub fn make(dto: DTOConnectionData) -> Result { - let category = EDBRepository::from_string(dto.category.clone()); + pub fn make(dto: DTODBConnectionData) -> Result { + let category = EDBRepository::from_string(&dto.category); if let None = category { let message = format!("Data base type '{}' not supported.", dto.category); return Err(ApiException::new(404, message)); diff --git a/src/domain/builder_db_service.rs b/src/domain/builder_db_service.rs index f23d05c..8c23879 100644 --- a/src/domain/builder_db_service.rs +++ b/src/domain/builder_db_service.rs @@ -1,6 +1,6 @@ use rust_db_manager_core::infrastructure::db_service::DBService; -use crate::{commons::exception::api_exception::ApiException, infrastructure::dto::db_service::dto_db_service::DTODBService}; +use crate::{commons::exception::api_exception::ApiException, infrastructure::dto::service::generate::dto_service_create_request::DTOServiceRequest}; use super::builder_db_connection_data::BuilderConnectionData; @@ -9,10 +9,15 @@ pub struct BuilderDBService { impl BuilderDBService { - pub fn make(dto: DTODBService) -> Result { + pub fn make(dto: DTOServiceRequest) -> Result { let connection_data = BuilderConnectionData::make(dto.connection_data)?; - let service = DBService::new(dto.name, dto.owner, connection_data); - Ok(service) + + let service = DBService::new(dto.name, dto.owner, dto.protected, dto.password, connection_data); + if service.is_err() { + return Err(ApiException::from(500, service.err().unwrap())); + } + + Ok(service.unwrap()) } } \ No newline at end of file diff --git a/src/domain/cookie/builder_cookie.rs b/src/domain/cookie/builder_cookie.rs new file mode 100644 index 0000000..be55a07 --- /dev/null +++ b/src/domain/cookie/builder_cookie.rs @@ -0,0 +1,70 @@ +use crate::commons::exception::auth_exception::AuthException; + +use super::{cookie::Cookie, same_site::SameSite}; + +pub(crate) struct BuilderCookie { +} + +impl BuilderCookie { + + pub(crate) fn make(cookie_string: &str) -> Result { + let mut parts: Vec<&str> = cookie_string.split(';').collect(); + + let code_value = parts.remove(0).trim() + .split('=') + .map(|f| String::from(f.trim())) + .collect::>(); + if code_value.len() != 2 { + let message = String::from("Invalid cookie format"); + return Err(AuthException::new_reset(422, message)); + } + + let code = code_value.get(0).cloned().unwrap(); + let value = code_value.get(1).cloned().unwrap(); + + let mut cookie = Cookie::new(code, value); + + for part in parts { + let key_value: Vec = part.trim() + .split('=') + .map(|f| String::from(f.trim())) + .collect::>(); + + let key = &key_value[0]; + let value = key_value.get(1).cloned(); + + match key.to_lowercase().as_str() { + "secure" => cookie.secure = Some(true), + "httponly" => cookie.http_only = Some(true), + "expires" => cookie.expiration = Some(value.unwrap_or_default()), + "domain" => cookie.domain = Some(value.unwrap_or_default()), + "path" => cookie.path = Some(value.unwrap_or_default()), + "max-age" => cookie.max_age = { + let string_max_age = value.unwrap_or_default(); + let max_age: Result = string_max_age.parse(); + if max_age.is_err() { + let exception = AuthException::new_reset(422, max_age.unwrap_err().to_string()); + return Err(exception); + } + Some(max_age.unwrap()) + }, + "samesite" => cookie.same_site = { + let string_samesite= value.unwrap_or_default(); + let samesite = SameSite::from_string(&string_samesite.clone()); + if samesite.is_none() { + let message = String::from(format!("Unknown Same Site value: '{}'", string_samesite)); + return Err(AuthException::new_reset(422, message)); + } + Some(samesite.unwrap()) + }, + _ => { + let message = String::from(format!("Unknown field code: '{}'", key)); + return Err(AuthException::new_reset(422, message)); + } + } + } + + Ok(cookie) + } + +} \ No newline at end of file diff --git a/src/domain/cookie/builder_jar.rs b/src/domain/cookie/builder_jar.rs new file mode 100644 index 0000000..c2294b1 --- /dev/null +++ b/src/domain/cookie/builder_jar.rs @@ -0,0 +1,73 @@ +use regex::Regex; + +use crate::commons::exception::auth_exception::AuthException; + +use super::{cookie::Cookie, jar::Jar}; + +pub(crate) struct BuilderJar { + last: usize, + buffer: Vec, + jar_string: String, + jar: Jar +} + +impl BuilderJar { + + pub(crate) fn make(jar_string: String) -> Result { + let mut instance = Self { + last: 0, + buffer: Vec::new(), + jar_string: jar_string, + jar: Jar::new() + }; + instance._make() + } + + fn _make(&mut self) -> Result { + let pattern = Regex::new(r";\s?[\w|.|-]+=").unwrap(); + + for capture in pattern.captures_iter(&self.jar_string.clone()) { + if let Some(index) = capture.get(0).map(|m| m.start()) { + self.manage_cookie(index)?; + self.last = index + 1; + } + } + + self.manage_cookie(self.jar_string.len())?; + + if !self.buffer.is_empty() { + self.flush_buffer()?; + } + + Ok(self.jar.clone()) + } + + fn manage_cookie(&mut self, index: usize) -> Result<(), AuthException> { + let jar_string = self.jar_string.clone(); + let fragment = jar_string[self.last..index].trim(); + println!("{}", self.buffer.join("; ")); + if self.last > 0 && Self::is_cookie_definition(fragment) { + self.flush_buffer()?; + } + + self.buffer.push(fragment.to_string()); + + Ok(()) + } + + fn flush_buffer(&mut self) -> Result<(), AuthException> { + self.jar.cookies.push(Cookie::from_string(&self.buffer.join("; "))?); + self.buffer.clear(); + Ok(()) + } + + fn is_cookie_definition(fragment: &str) -> bool { + let f = fragment.to_lowercase(); + + !f.starts_with("domain") && !f.starts_with("path") + && !f.starts_with("expires") && !f.starts_with("max-age") + && !f.starts_with("samesite") && !f.starts_with("secure") + && !f.starts_with("httponly") + } + +} \ No newline at end of file diff --git a/src/domain/cookie/cookie.rs b/src/domain/cookie/cookie.rs new file mode 100644 index 0000000..83c2d8e --- /dev/null +++ b/src/domain/cookie/cookie.rs @@ -0,0 +1,66 @@ +use crate::commons::exception::auth_exception::AuthException; + +use super::{builder_cookie::BuilderCookie, same_site::SameSite}; + +#[derive(Debug, Clone)] +pub struct Cookie { + pub code: String, + pub value: String, + pub domain: Option, + pub path: Option, + pub expiration: Option, + pub max_age: Option, + pub secure: Option, + pub http_only: Option, + pub same_site: Option, +} + +impl Cookie { + + pub fn new(code: String, value: String) -> Self { + Cookie { + code: code, value: value, domain: None, + path: None, expiration: None, max_age: None, + secure: None, http_only: None, same_site: None + } + } + + pub fn to_string(&self) -> String { + let mut cookie_string = format!("{}={}", self.code, self.value); + + if let Some(domain) = &self.domain { + cookie_string.push_str(&format!("; Domain={}", domain)); + } + + if let Some(path) = &self.path { + cookie_string.push_str(&format!("; Path={}", path)); + } + + if let Some(expiration) = &self.expiration { + cookie_string.push_str(&format!("; Expires={}", expiration)); + } + + if let Some(max_age) = self.max_age { + cookie_string.push_str(&format!("; Max-Age={}", max_age)); + } + + if let Some(true) = self.secure { + cookie_string.push_str("; Secure"); + } + + if let Some(true) = self.http_only { + cookie_string.push_str("; HttpOnly"); + } + + if let Some(same_site) = &self.same_site { + cookie_string.push_str(&format!("; SameSite={}", same_site)); + } + + cookie_string + } + + pub fn from_string(cookie_string: &str) -> Result { + BuilderCookie::make(cookie_string) + } + +} \ No newline at end of file diff --git a/src/domain/cookie/jar.rs b/src/domain/cookie/jar.rs new file mode 100644 index 0000000..5b4c1cd --- /dev/null +++ b/src/domain/cookie/jar.rs @@ -0,0 +1,26 @@ +use crate::commons::exception::auth_exception::AuthException; + +use super::{builder_jar::BuilderJar, cookie::Cookie}; + +#[derive(Debug, Clone)] +pub struct Jar { + pub(crate) cookies: Vec +} + +impl Jar { + + pub fn new() -> Self { + Jar { + cookies: Vec::new() + } + } + + pub fn from_string(jar_string: &str) -> Result { + BuilderJar::make(jar_string.to_string()) + } + + pub fn find(&self, code: &str) -> Option { + self.cookies.iter().find(|c| c.code == code).cloned() + } + +} \ No newline at end of file diff --git a/src/domain/cookie/same_site.rs b/src/domain/cookie/same_site.rs new file mode 100644 index 0000000..7553f03 --- /dev/null +++ b/src/domain/cookie/same_site.rs @@ -0,0 +1,29 @@ +use std::fmt::{Display, Formatter, Result}; + +#[derive(Debug, Clone)] +pub enum SameSite { + Strict, + Lax, + None +} + +impl Display for SameSite { + + fn fmt(&self, f: &mut Formatter) -> Result { + write!(f, "{:?}", self) + } + +} + +impl SameSite { + + pub fn from_string(category: &str) -> Option { + match category.to_lowercase().as_str() { + "strict" => Some(SameSite::Strict), + "lax" => Some(SameSite::Lax), + "none" => Some(SameSite::None), + _ => None, + } + } + +} \ No newline at end of file diff --git a/src/domain/utils.rs b/src/domain/utils.rs deleted file mode 100644 index 1fde8de..0000000 --- a/src/domain/utils.rs +++ /dev/null @@ -1,15 +0,0 @@ -use rust_db_manager_core::infrastructure::db_service::DBService; - -use crate::infrastructure::dto::db_service::dto_db_service::DTODBService; - -pub trait DTOBuilder { - fn from_dto(dto: K) -> T; -} - -impl DTOBuilder for DBService { - - fn from_dto(dto: DTODBService) -> DBService { - - } - -} \ No newline at end of file diff --git a/src/infrastructure/controller.rs b/src/infrastructure/controller.rs deleted file mode 100644 index 8fbb1f3..0000000 --- a/src/infrastructure/controller.rs +++ /dev/null @@ -1,79 +0,0 @@ -use axum::{body::Body, extract::{Path, Query}, http::{Response, StatusCode}, response::IntoResponse, routing::{get, post}, Json, Router}; - -use rust_db_manager_core::commons::configuration::configuration::Configuration; - -use crate::{commons::{configuration::web_configuration::WebConfiguration, exception::api_exception::ApiException}, domain::builder_db_service::BuilderDBService}; - -use super::{dto::{db_service::{dto_db_service::DTODBService, dto_db_service_lite::DTODBServiceLite}, dto_server_status::DTOServerStatus, pagination::{dto_paginated_collection::DTOPaginatedCollection, dto_query_pagination::DTOQueryPagination}}, pagination::Pagination}; - -pub struct Controller{ -} - -impl Controller { - - pub fn route(router: Router) -> Router { - router - .route("/status", get(Controller::status)) - .route("/services", get(Controller::services)) - .route("/:service/status", get(Controller::service_status)) - .route("/:service", post(Controller::insert_service)) - } - - async fn status() -> (StatusCode, Json) { - let result = WebConfiguration::as_dto(); - (StatusCode::ACCEPTED, Json(result)) - } - - async fn services(Query(params): Query) -> (StatusCode, Json>) { - let services = Configuration::find_services(); - let dto = DTODBServiceLite::from_vec(services); - let result = Pagination::paginate(params, dto); - (StatusCode::ACCEPTED, Json(result)) - } - - async fn insert_service(Json(dto): Json) -> Result<(StatusCode, String), impl IntoResponse> { - let o_service = BuilderDBService::make(dto); - if let Err(error) = o_service { - return Err(error.into_response()); - } - - let service = o_service.unwrap(); - - let db_service = Configuration::push_service(service.clone()); - if let Err(error) = db_service { - let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); - return Err(exception.into_response()); - } - - Ok((StatusCode::ACCEPTED, service.name())) - } - - async fn service_status(Path(service): Path) -> Result<(StatusCode, String), impl IntoResponse> { - let db_service = Configuration::find_service(service); - if db_service.is_none() { - return Err(Controller::not_found()); - } - - let result = db_service.unwrap().instance().await; - if let Err(error) = result { - let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); - return Err(exception.into_response()); - } - - let status = result.unwrap().status().await; - if let Err(error) = status { - let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); - return Err(exception.into_response()); - } - - Ok((StatusCode::ACCEPTED, String::from("Service up."))) - } - - fn not_found() -> Response { - let error = ApiException::new( - StatusCode::NOT_FOUND.as_u16(), - String::from("Not found")); - return error.into_response(); - } - -} \ No newline at end of file diff --git a/src/infrastructure/controller_collection.rs b/src/infrastructure/controller_collection.rs new file mode 100644 index 0000000..e040911 --- /dev/null +++ b/src/infrastructure/controller_collection.rs @@ -0,0 +1,239 @@ +use axum::{ + extract::{DefaultBodyLimit, Path}, + http::StatusCode, + middleware, + response::IntoResponse, + routing::{delete, get, post}, + Json, Router, +}; +use rust_db_manager_core::{ + commons::configuration::configuration::Configuration, + domain::{ + collection::generate_collection_query::GenerateCollectionQuery, + filter::{collection_query::CollectionQuery, data_base_query::DataBaseQuery}, + }, +}; + +use crate::commons::exception::api_exception::ApiException; + +use super::{ + dto::{ + collection::{dto_generate_collection_query::DTOGenerateCollectionQuery, dto_rename_collection_query::DTORenameCollectionQuery}, document::{dto_document_data::DTODocumentData, dto_document_schema::DTODocumentSchema}, table::dto_table_data_group::DTOTableDataGroup + }, + handler, utils, +}; + +pub struct ControllerCollection { +} + +impl ControllerCollection { + + pub fn route(router: Router) -> Router { + router + .route("/api/v1/service/:service/data-base/:data_base/collection/:collection/import", post(Self::import)) + .layer(DefaultBodyLimit::max(52428800 )) + .route("/api/v1/service/:service/data-base/:data_base/collection", get(Self::find_all)) + .route("/api/v1/service/:service/data-base/:data_base/collection", post(Self::insert)) + .route("/api/v1/service/:service/data-base/:data_base/collection/:collection", delete(Self::delete)) + .route("/api/v1/service/:service/data-base/:data_base/collection/:collection/metadata", get(Self::metadata)) + .route("/api/v1/service/:service/data-base/:data_base/collection/:collection/schema", get(Self::schema)) + .route("/api/v1/service/:service/data-base/:data_base/collection/:collection/rename", post(Self::rename)) + .route("/api/v1/service/:service/data-base/:data_base/collection/:collection/export", get(Self::export)) + .route_layer(middleware::from_fn(handler::autentication_handler)) + } + + async fn find_all(Path((service, data_base)): Path<(String, String)>) -> Result>, impl IntoResponse> { + let o_db_service = Configuration::find_service(&service); + if o_db_service.is_none() { + return Err(utils::not_found()); + } + + let result = o_db_service.unwrap().instance().await; + if let Err(error) = result { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + let query = DataBaseQuery::from(data_base); + + let collections = result.unwrap().collection_find_all(&query).await; + if let Err(error) = collections { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + Ok(Json(collections.unwrap())) + } + + async fn insert(Path((service, _)): Path<(String, String)>, Json(dto): Json) -> Result { + let o_db_service = Configuration::find_service(&service); + if o_db_service.is_none() { + return Err(utils::not_found()); + } + + let result = o_db_service.unwrap().instance().await; + if let Err(error) = result { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + let query = dto.from_dto(); + if let Err(exception) = query { + return Err(exception.into_response()); + } + + let collection = result.unwrap().collection_create(&query.unwrap()).await; + if let Err(error) = collection { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + Ok(StatusCode::ACCEPTED) + } + + async fn delete(Path((service, data_base, collection)): Path<(String, String, String)>) -> Result { + let o_db_service = Configuration::find_service(&service); + if o_db_service.is_none() { + return Err(utils::not_found()); + } + + let result = o_db_service.unwrap().instance().await; + if let Err(error) = result { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + let query = GenerateCollectionQuery::from_collection(data_base, collection); + + let collection = result.unwrap().collection_drop(&query).await; + if let Err(error) = collection { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + Ok(StatusCode::ACCEPTED) + } + + async fn metadata(Path((service, data_base, collection)): Path<(String, String, String)>) -> Result>, impl IntoResponse> { + let o_db_service = Configuration::find_service(&service); + if o_db_service.is_none() { + return Err(utils::not_found()); + } + + let result = o_db_service.unwrap().instance().await; + if let Err(error) = result { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + let query = CollectionQuery::from(data_base, collection); + + let metadata = result.unwrap().collection_metadata(&query).await; + if let Err(error) = metadata { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + let dto = metadata.unwrap().iter() + .map(|g| DTOTableDataGroup::from(g)) + .collect(); + + Ok(Json(dto)) + } + + async fn schema(Path((service, data_base, collection)): Path<(String, String, String)>) -> Result, impl IntoResponse> { + let o_db_service = Configuration::find_service(&service); + if o_db_service.is_none() { + return Err(utils::not_found()); + } + + let result = o_db_service.unwrap().instance().await; + if let Err(error) = result { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + let query = CollectionQuery::from(data_base, collection); + + let schema = result.unwrap().schema(&query).await; + if let Err(error) = schema { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + Ok(Json(DTODocumentSchema::from(&schema.unwrap()))) + } + + async fn rename(Path((service, data_base, collection)): Path<(String, String, String)>, Json(dto): Json) -> Result { + let o_db_service = Configuration::find_service(&service); + if o_db_service.is_none() { + return Err(utils::not_found()); + } + + let result = o_db_service.unwrap().instance().await; + if let Err(error) = result { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + let query = CollectionQuery::from(data_base, collection); + + let documents = result.unwrap().collection_rename(&query, &dto.collection).await; + if let Err(error) = documents { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + Ok(StatusCode::OK) + } + + async fn export(Path((service, data_base, collection)): Path<(String, String, String)>) -> Result>, impl IntoResponse> { + let o_db_service = Configuration::find_service(&service); + if o_db_service.is_none() { + return Err(utils::not_found()); + } + + let result = o_db_service.unwrap().instance().await; + if let Err(error) = result { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + let query = CollectionQuery::from(data_base, collection); + + let documents = result.unwrap().collection_export(&query).await; + if let Err(error) = documents { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + Ok(Json(documents.unwrap().iter() + .map(|d| DTODocumentData::from(d)) + .collect()) + ) + } + + async fn import(Path((service, data_base, collection)): Path<(String, String, String)>, documents: Json>) -> Result { + let o_db_service = Configuration::find_service(&service); + if o_db_service.is_none() { + return Err(utils::not_found()); + } + + let result = o_db_service.unwrap().instance().await; + if let Err(error) = result { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + let query = CollectionQuery::from(data_base, collection); + + let result = result.unwrap().collection_import(&query, documents.to_vec()).await; + if let Err(error) = result { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + Ok(StatusCode::OK) + } + +} \ No newline at end of file diff --git a/src/infrastructure/controller_database.rs b/src/infrastructure/controller_database.rs new file mode 100644 index 0000000..fdf92f3 --- /dev/null +++ b/src/infrastructure/controller_database.rs @@ -0,0 +1,129 @@ +use axum::{ + extract::Path, + http::StatusCode, + middleware, + response::IntoResponse, + routing::{delete, get, post}, + Json, Router, +}; +use rust_db_manager_core::{ + commons::configuration::configuration::Configuration, + domain::{data_base::generate_database_query::GenerateDatabaseQuery, filter::data_base_query::DataBaseQuery}, +}; + +use crate::commons::exception::api_exception::ApiException; + +use super::{ + dto::{data_base::dto_generate_data_base_query::DTOGenerateDatabaseQuery, table::dto_table_data_group::DTOTableDataGroup}, + handler, utils, +}; + +pub struct ControllerDataBase { +} + +impl ControllerDataBase { + + pub fn route(router: Router) -> Router { + router + .route("/api/v1/service/:service/data-base", get(Self::find_all)) + .route("/api/v1/service/:service/data-base", post(Self::insert)) + .route("/api/v1/service/:service/data-base/:data_base", delete(Self::delete)) + .route("/api/v1/service/:service/data-base/:data_base/metadata", get(Self::metadata)) + .route_layer(middleware::from_fn(handler::autentication_handler)) + } + + async fn find_all(Path(service): Path) -> Result>, impl IntoResponse> { + let o_db_service = Configuration::find_service(&service); + if o_db_service.is_none() { + return Err(utils::not_found()); + } + + let result = o_db_service.unwrap().instance().await; + if let Err(error) = result { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + let collection = result.unwrap().data_base_find_all().await; + if let Err(error) = collection { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + Ok(Json(collection.unwrap())) + } + + async fn insert(Path(service): Path, Json(dto): Json) -> Result { + let o_db_service = Configuration::find_service(&service); + if o_db_service.is_none() { + return Err(utils::not_found()); + } + + let result = o_db_service.unwrap().instance().await; + if let Err(error) = result { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + let query = GenerateDatabaseQuery::new(dto.data_base); + + let collection = result.unwrap().data_base_create(&query).await; + if let Err(error) = collection { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + Ok(StatusCode::ACCEPTED) + } + + async fn delete(Path((service, data_base)): Path<(String, String)>) -> Result { + let o_db_service = Configuration::find_service(&service); + if o_db_service.is_none() { + return Err(utils::not_found()); + } + + let result = o_db_service.unwrap().instance().await; + if let Err(error) = result { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + let query = GenerateDatabaseQuery::new(data_base); + + let collection = result.unwrap().data_base_drop(&query).await; + if let Err(error) = collection { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + Ok(StatusCode::ACCEPTED) + } + + async fn metadata(Path((service, data_base)): Path<(String, String)>) -> Result>, impl IntoResponse> { + let o_db_service = Configuration::find_service(&service); + if o_db_service.is_none() { + return Err(utils::not_found()); + } + + let result = o_db_service.unwrap().instance().await; + if let Err(error) = result { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + let query = DataBaseQuery::from(data_base); + + let metadata = result.unwrap().data_base_metadata(&query).await; + if let Err(error) = metadata { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + let dto = metadata.unwrap().iter() + .map(|g| DTOTableDataGroup::from(g)) + .collect(); + + Ok(Json(dto)) + } + +} \ No newline at end of file diff --git a/src/infrastructure/controller_document.rs b/src/infrastructure/controller_document.rs new file mode 100644 index 0000000..f3c6394 --- /dev/null +++ b/src/infrastructure/controller_document.rs @@ -0,0 +1,198 @@ +use axum::{ + extract::{Path, Query}, + http::StatusCode, + middleware, + response::IntoResponse, + routing::{delete, get, post, put}, + Json, Router, +}; +use rust_db_manager_core::{ + commons::{ + configuration::configuration::Configuration, utils::document_keys_to_filter_element, + }, + domain::filter::{collection_query::CollectionQuery, document_query::DocumentQuery}, +}; + +use crate::commons::exception::api_exception::ApiException; + +use super::{ + dto::{ + collection::dto_collection_data::DTOCollectionData, document::{dto_document_data::DTODocumentData, dto_document_key::DTODocumentKey}, dto_create_document::DTOCreateDocument, dto_update_document::DTOUpdateDocument, pagination::dto_query_pagination::DTOQueryPagination + }, + handler, utils, +}; + +pub struct ControllerDocument { +} + +impl ControllerDocument { + + pub fn route(router: Router) -> Router { + router + .route("/api/v1/service/:service/data-base/:data_base/collection/:collection/document/find", get(Self::find_all)) + .route("/api/v1/service/:service/data-base/:data_base/collection/:collection/document/find", post(Self::find)) + .route("/api/v1/service/:service/data-base/:data_base/collection/:collection/document/action", post(Self::insert)) + .route("/api/v1/service/:service/data-base/:data_base/collection/:collection/document/action", put(Self::update)) + .route("/api/v1/service/:service/data-base/:data_base/collection/:collection/document/action", delete(Self::delete)) + .route_layer(middleware::from_fn(handler::autentication_handler)) + } + + async fn find(Path((service, data_base, collection)): Path<(String, String, String)>, Json(dto): Json>) -> Result, impl IntoResponse> { + let o_db_service = Configuration::find_service(&service); + if o_db_service.is_none() { + return Err(utils::not_found()); + } + + let result = o_db_service.unwrap().instance().await; + if let Err(error) = result { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + let mut keys = Vec::new(); + for dto_key in dto { + let key = dto_key.from_dto(); + if let Err(exception) = key { + return Err(exception.into_response()); + } + keys.push(key.unwrap()); + } + + let filter = document_keys_to_filter_element(keys); + let query = DocumentQuery::from_filter(data_base, collection, filter); + + let r_document = result.unwrap().find(&query).await; + if let Err(error) = r_document { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + let document = r_document.unwrap(); + + if let None = document { + let exception = ApiException::new(StatusCode::NOT_FOUND.as_u16(), String::from("Document not found.")); + return Err(exception.into_response()); + } + + Ok(Json(DTODocumentData::from(&document.unwrap()))) + } + + async fn find_all(Path((service, data_base, collection)): Path<(String, String, String)>, Query(params): Query) -> Result, impl IntoResponse> { + let o_db_service = Configuration::find_service(&service); + if o_db_service.is_none() { + return Err(utils::not_found()); + } + + let result = o_db_service.unwrap().instance().await; + if let Err(error) = result { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + let query = DocumentQuery::from(data_base, collection, Some(params.limit), Some(params.offset), None); + + let data = result.unwrap().find_all(&query).await; + if let Err(error) = data { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + Ok(Json(DTOCollectionData::from(&data.unwrap()))) + } + + async fn insert(Path((service, data_base, collection)): Path<(String, String, String)>, Json(dto): Json) -> Result, impl IntoResponse> { + let o_db_service = Configuration::find_service(&service); + if o_db_service.is_none() { + return Err(utils::not_found()); + } + + let result = o_db_service.unwrap().instance().await; + if let Err(error) = result { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + let query = CollectionQuery::from(data_base, collection); + + let document = result.unwrap().insert(&query, &dto.document).await; + if let Err(error) = document { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + Ok(Json(DTODocumentData::from(&document.unwrap()))) + } + + async fn update(Path((service, data_base, collection)): Path<(String, String, String)>, Json(dto): Json) -> Result>, impl IntoResponse> { + let o_db_service = Configuration::find_service(&service); + if o_db_service.is_none() { + return Err(utils::not_found()); + } + + let result = o_db_service.unwrap().instance().await; + if let Err(error) = result { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + let mut keys = Vec::new(); + for dto_key in dto.keys { + let key = dto_key.from_dto(); + if let Err(exception) = key { + return Err(exception.into_response()); + } + keys.push(key.unwrap()); + } + + let filter = document_keys_to_filter_element(keys); + let query = DocumentQuery::from_filter(data_base, collection, filter); + + let documents = result.unwrap().update(&query, &dto.document).await; + if let Err(error) = documents { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + Ok(Json(documents.unwrap().iter() + .map(|d| DTODocumentData::from(d)) + .collect()) + ) + } + + async fn delete(Path((service, data_base, collection)): Path<(String, String, String)>, Json(dto): Json>) -> Result>, impl IntoResponse> { + let o_db_service = Configuration::find_service(&service); + if o_db_service.is_none() { + return Err(utils::not_found()); + } + + let result = o_db_service.unwrap().instance().await; + if let Err(error) = result { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + let mut keys = Vec::new(); + for dto_key in dto { + let key = dto_key.from_dto(); + if let Err(exception) = key { + return Err(exception.into_response()); + } + keys.push(key.unwrap()); + } + + let filter = document_keys_to_filter_element(keys); + let query = DocumentQuery::from_filter(data_base, collection, filter); + + let documents = result.unwrap().delete(&query).await; + if let Err(error) = documents { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + Ok(Json(documents.unwrap().iter() + .map(|d| DTODocumentData::from(d)) + .collect()) + ) + } + +} \ No newline at end of file diff --git a/src/infrastructure/controller_server.rs b/src/infrastructure/controller_server.rs new file mode 100644 index 0000000..6757ffd --- /dev/null +++ b/src/infrastructure/controller_server.rs @@ -0,0 +1,37 @@ +use axum::{ + http::StatusCode, + routing::get, + Json, Router, +}; + +use rust_db_manager_core::infrastructure::repository::e_db_repository::EDBRepository; + +use crate::commons::configuration::web_configuration::WebConfiguration; + +use super::{ + db_assets::WebEDBRepository, + dto::{dto_server_status::DTOServerStatus, service::definition::dto_service_category_lite::DTOServiceCategoryLite} +}; + +pub struct ControllerServer { +} + +impl ControllerServer { + + pub fn route(router: Router) -> Router { + router + .route("/api/v1/metadata", get(Self::metadata)) + .route("/api/v1/available", get(Self::available)) + } + + async fn metadata() -> (StatusCode, Json) { + let result = WebConfiguration::as_dto(); + (StatusCode::OK, Json(result)) + } + + async fn available() -> (StatusCode, Json>) { + let dto = EDBRepository::availables(); + (StatusCode::OK, Json(dto)) + } + +} \ No newline at end of file diff --git a/src/infrastructure/controller_service.rs b/src/infrastructure/controller_service.rs new file mode 100644 index 0000000..0de66d2 --- /dev/null +++ b/src/infrastructure/controller_service.rs @@ -0,0 +1,257 @@ +use axum::{ + body::Body, + extract::{Path, Query}, + http::{header::SET_COOKIE, HeaderMap, Response, StatusCode}, + middleware, + response::IntoResponse, + routing::{delete, get, patch, post}, + Json, Router, +}; + +use rust_db_manager_core::{ + commons::configuration::configuration::Configuration, infrastructure::db_service::DBService, +}; + +use crate::{ + commons::exception::{api_exception::ApiException, auth_exception::AuthException}, + domain::{builder_db_service::BuilderDBService, cookie::cookie::Cookie}, +}; + +use super::{ + dto::{ + collection::dto_collection_definition::DTOCollectionDefinition, pagination::{ + dto_paginated_collection::DTOPaginatedCollection, + dto_query_pagination::DTOQueryPagination, + }, service::{ + definition::{ + dto_service::DTOService, + dto_service_lite::DTOServiceLite, + }, + generate::{ + dto_service_create_request::DTOServiceRequest, + dto_service_suscribe_request::DTOServiceSuscribeRequest, + }, + }, table::dto_table_data_group::DTOTableDataGroup + }, + handler, + pagination::Pagination, + services_jwt::ServicesJWT, + utils::{self, find_token}, +}; + +pub struct ControllerService { +} + +impl ControllerService { + + pub fn route(router: Router) -> Router { + router + .route("/api/v1/service/:service", get(Self::find)) + .route("/api/v1/service/:service", delete(Self::delete)) + .route("/api/v1/service/:service/status", get(Self::status)) + .route("/api/v1/service/:service/metadata", get(Self::metadata)) + .route("/api/v1/service/:service/schema", get(Self::schema)) + .route_layer(middleware::from_fn(handler::autentication_handler)) + + .route("/api/v1/service", get(Self::find_all)) + .route("/api/v1/service", post(Self::insert)) + .route("/api/v1/service", patch(Self::suscribe)) + } + + async fn find(Path(service): Path) -> Result,impl IntoResponse> { + let o_db_service = Configuration::find_service(&service); + if o_db_service.is_none() { + return Err(utils::not_found()); + } + + Ok(Json(DTOService::from(o_db_service.unwrap()))) + } + + async fn delete(headers: HeaderMap, Path(service): Path) -> impl IntoResponse { + let o_db_service = Configuration::find_service(&service); + if o_db_service.is_none() { + return Err(utils::not_found()); + } + + let db_service = o_db_service.unwrap(); + + let r_cookie = Self::remove_token(headers, &db_service); + if let Err(exception) = r_cookie { + return Err(exception.into_response()); + } + + Configuration::remove_service(db_service); + + Ok(Self::build_token_response(r_cookie.unwrap(), Body::empty())) + } + + async fn status(Path(service): Path) -> Result<(StatusCode, String), impl IntoResponse> { + let o_db_service = Configuration::find_service(&service); + if o_db_service.is_none() { + return Err(utils::not_found()); + } + + let result = o_db_service.unwrap().instance().await; + if let Err(error) = result { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + let status = result.unwrap().status().await; + if let Err(error) = status { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + Ok((StatusCode::ACCEPTED, String::from("listening"))) + } + + async fn metadata(Path(service): Path) -> Result>, impl IntoResponse> { + let o_db_service = Configuration::find_service(&service); + if o_db_service.is_none() { + return Err(utils::not_found()); + } + + let result = o_db_service.unwrap().instance().await; + if let Err(error) = result { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + let metadata = result.unwrap().metadata().await; + if let Err(error) = metadata { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + let dto = metadata.unwrap().iter() + .map(|g| DTOTableDataGroup::from(g)) + .collect(); + + Ok(Json(dto)) + } + + async fn schema(Path(service): Path) -> Result, impl IntoResponse> { + let o_db_service = Configuration::find_service(&service); + if o_db_service.is_none() { + return Err(utils::not_found()); + } + + let result = o_db_service.unwrap().instance().await; + if let Err(error) = result { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + let definition = result.unwrap().collection_accept_schema().await; + if let Err(error) = definition { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), error); + return Err(exception.into_response()); + } + + Ok(Json(DTOCollectionDefinition::from(definition.unwrap()))) + } + + async fn find_all(Query(params): Query) -> (StatusCode, Json>) { + let services = Configuration::find_services(); + let dto = services.iter().map(|s| DTOServiceLite::from(s)).collect(); + let result = Pagination::paginate(params, dto); + (StatusCode::ACCEPTED, Json(result)) + } + + async fn insert(headers: HeaderMap, Json(dto): Json) -> impl IntoResponse { + let o_service = BuilderDBService::make(dto); + if let Err(error) = o_service { + return Err(error.into_response()); + } + + let service = &o_service.unwrap(); + + let r_cookie = Self::make_token(headers, service); + if let Err(exception) = r_cookie { + return Err(exception.into_response()); + } + + let db_service = Configuration::push_service(service); + if let Err(exception) = db_service { + let exception = ApiException::from(StatusCode::INTERNAL_SERVER_ERROR.as_u16(), exception); + return Err(exception.into_response()); + } + + Ok(Self::build_token_response(r_cookie.unwrap(), Body::empty())) + } + + async fn suscribe(headers: HeaderMap, Json(dto): Json) -> impl IntoResponse { + let o_db_service = Configuration::find_service(&dto.name); + if o_db_service.is_none() { + let exception = ApiException::new(StatusCode::NOT_FOUND.as_u16(), String::from("Service not found.")); + return Err(exception.into_response()); + } + + let db_service = &o_db_service.unwrap(); + + if db_service.is_authorized(dto.password).is_err() { + let exception = ApiException::new(StatusCode::UNAUTHORIZED.as_u16(), String::from("Authentication error.")); + return Err(exception.into_response()); + } + + let r_cookie = Self::make_token(headers, db_service); + if let Err(exception) = r_cookie { + return Err(exception.into_response()); + } + + Ok(Self::build_token_response(r_cookie.unwrap(), Body::empty())) + } + + fn make_token(headers: HeaderMap, service: &DBService) -> Result, AuthException> { + let o_cookie = find_token(headers); + if o_cookie.is_err() { + return Err(o_cookie.unwrap_err()); + } + + match o_cookie.unwrap() { + Some(cookie) => { + if !service.is_protected() { + return Ok(Some(cookie)); + } + + Ok(Some(ServicesJWT::update(&cookie.value, service)?)) + }, + None => { + if !service.is_protected() { + return Ok(None); + } + Ok(Some(ServicesJWT::sign(service)?)) + }, + } + } + + fn remove_token(headers: HeaderMap, service: &DBService) -> Result, AuthException> { + let o_cookie = find_token(headers); + if o_cookie.is_err() { + return Err(o_cookie.unwrap_err()); + } + + match o_cookie.unwrap() { + Some(cookie) => { + if !service.is_protected() { + return Ok(Some(cookie)); + } + Ok(Some(ServicesJWT::remove(&cookie.value, service)?)) + }, + None => return Ok(None), + } + } + + fn build_token_response(cookie: Option, body: Body) -> impl IntoResponse { + let mut builder = Response::builder(); + if cookie.is_some() { + builder = builder.header(SET_COOKIE, cookie.unwrap().to_string()); + } + + builder.status(StatusCode::OK) + .body(body) + .unwrap() + } + +} \ No newline at end of file diff --git a/src/infrastructure/db_assets.rs b/src/infrastructure/db_assets.rs index c661b41..943c2aa 100644 --- a/src/infrastructure/db_assets.rs +++ b/src/infrastructure/db_assets.rs @@ -1,21 +1,26 @@ use rust_db_manager_core::infrastructure::repository::e_db_repository::EDBRepository; -use super::dto::db_service::dto_db_resources::DTODBResources; +use super::dto::service::definition::{dto_service_category_lite::DTOServiceCategoryLite, dto_service_resources::DTOServiceResources}; pub trait WebEDBRepository { - - fn resources(&self) -> DTODBResources; - + fn availables() -> Vec; + fn resources(&self) -> DTOServiceResources; } impl WebEDBRepository for EDBRepository { - fn resources(&self) -> DTODBResources { + fn availables() -> Vec { + EDBRepository::items().iter() + .map(|e| DTOServiceCategoryLite::from(e)) + .collect() + } + + fn resources(&self) -> DTOServiceResources { match self { - EDBRepository::MongoDB => DTODBResources::new( + EDBRepository::MongoDB => DTOServiceResources::new( String::from("https://www.mongodb.com/"), String::from("#00ED64"), - String::from("https://thumbs.bfldr.com/at/hj345wvxsvpbc82vchqcj9qh?expiry=1714820885&fit=bounds&height=162&sig=NTU3MjQ1YzFjYzljYzFhY2UxNjI0ZDA4ZjhjNTc4ZDI2YzViNmMxOQ%3D%3D&width=262") + String::from("data:image/png;base64,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") ), } } diff --git a/src/infrastructure/dto/collection/dto_collection_data.rs b/src/infrastructure/dto/collection/dto_collection_data.rs new file mode 100644 index 0000000..ce4c309 --- /dev/null +++ b/src/infrastructure/dto/collection/dto_collection_data.rs @@ -0,0 +1,27 @@ +use rust_db_manager_core::domain::collection::collection_data::CollectionData; +use serde::Serialize; + +use crate::infrastructure::dto::document::dto_document_data::DTODocumentData; + +#[derive(Clone, Serialize)] +pub struct DTOCollectionData { + total: usize, + limit: Option, + offset: Option, + documents: Vec +} + +impl DTOCollectionData { + + pub fn from(data: &CollectionData) -> Self { + Self { + total: data.total(), + limit: data.limit(), + offset: data.offset(), + documents: data.documents().iter() + .map(|d| DTODocumentData::from(d)) + .collect() + } + } + +} \ No newline at end of file diff --git a/src/infrastructure/dto/collection/dto_collection_definition.rs b/src/infrastructure/dto/collection/dto_collection_definition.rs new file mode 100644 index 0000000..75ef7ee --- /dev/null +++ b/src/infrastructure/dto/collection/dto_collection_definition.rs @@ -0,0 +1,27 @@ +use rust_db_manager_core::domain::collection::collection_definition::CollectionDefinition; +use serde::Serialize; + +use crate::infrastructure::dto::field::{definition::dto_field_definition::DTOFieldDefinition, generate::dto_field_data::DTOFieldData}; + +#[derive(Clone, Serialize)] +pub struct DTOCollectionDefinition { + swrelational: bool, + definition: Vec, + defaults: Vec +} + +impl DTOCollectionDefinition { + + pub fn from(definition: CollectionDefinition) -> Self { + Self { + swrelational: definition.is_relational(), + definition: definition.definition().iter() + .map(|f| DTOFieldDefinition::from(f)) + .collect(), + defaults: definition.defaults().iter() + .map(|d| DTOFieldData::from(d)) + .collect() + } + } + +} \ No newline at end of file diff --git a/src/infrastructure/dto/collection/dto_generate_collection_query.rs b/src/infrastructure/dto/collection/dto_generate_collection_query.rs new file mode 100644 index 0000000..8e3d129 --- /dev/null +++ b/src/infrastructure/dto/collection/dto_generate_collection_query.rs @@ -0,0 +1,25 @@ +use rust_db_manager_core::domain::collection::generate_collection_query::GenerateCollectionQuery; +use serde::Deserialize; + +use crate::{commons::exception::api_exception::ApiException, infrastructure::dto::field::generate::dto_field_data::DTOFieldData}; + +#[derive(Clone, Deserialize)] +pub struct DTOGenerateCollectionQuery { + data_base: String, + collection: String, + fields: Option> +} + +impl DTOGenerateCollectionQuery { + + pub fn from_dto(&self) -> Result { + let mut fields = Vec::new(); + if let Some(dtos) = self.fields.clone() { + for dto in dtos { + fields.push(dto.from_dto()?); + } + } + Ok(GenerateCollectionQuery::new(self.data_base.clone(), self.collection.clone(), fields)) + } + +} \ No newline at end of file diff --git a/src/infrastructure/dto/collection/dto_rename_collection_query.rs b/src/infrastructure/dto/collection/dto_rename_collection_query.rs new file mode 100644 index 0000000..8227019 --- /dev/null +++ b/src/infrastructure/dto/collection/dto_rename_collection_query.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Clone, Deserialize)] +pub struct DTORenameCollectionQuery { + pub collection: String +} \ No newline at end of file diff --git a/src/infrastructure/dto/data_base/dto_generate_data_base_query.rs b/src/infrastructure/dto/data_base/dto_generate_data_base_query.rs new file mode 100644 index 0000000..b401090 --- /dev/null +++ b/src/infrastructure/dto/data_base/dto_generate_data_base_query.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Clone, Deserialize)] +pub struct DTOGenerateDatabaseQuery { + pub data_base: String +} \ No newline at end of file diff --git a/src/infrastructure/dto/db_service/dto_db_service.rs b/src/infrastructure/dto/db_service/dto_db_service.rs deleted file mode 100644 index e752d4e..0000000 --- a/src/infrastructure/dto/db_service/dto_db_service.rs +++ /dev/null @@ -1,10 +0,0 @@ -use serde::Deserialize; - -use super::dto_db_connection_data::DTOConnectionData; - -#[derive(Clone, Deserialize)] -pub struct DTODBService { - pub name: String, - pub owner: String, - pub connection_data: DTOConnectionData -} \ No newline at end of file diff --git a/src/infrastructure/dto/db_service/dto_db_service_lite.rs b/src/infrastructure/dto/db_service/dto_db_service_lite.rs deleted file mode 100644 index d637c92..0000000 --- a/src/infrastructure/dto/db_service/dto_db_service_lite.rs +++ /dev/null @@ -1,18 +0,0 @@ -use rust_db_manager_core::infrastructure::db_service_lite::DBServiceLite; -use serde::{Deserialize, Serialize}; - -use super::dto_db_service_web_category::DTODBServiceWebCategory; - -#[derive(Clone, Serialize, Deserialize)] -pub struct DTODBServiceLite { - pub name: String, - pub category: DTODBServiceWebCategory -} - -impl DTODBServiceLite { - - pub fn from_vec(collection: Vec) -> Vec { - collection.iter().map(|s| DTODBServiceLite{name: s.name(), category: DTODBServiceWebCategory::from(s.category())}).collect() - } - -} \ No newline at end of file diff --git a/src/infrastructure/dto/db_service/dto_db_service_web_category.rs b/src/infrastructure/dto/db_service/dto_db_service_web_category.rs deleted file mode 100644 index dc5fd54..0000000 --- a/src/infrastructure/dto/db_service/dto_db_service_web_category.rs +++ /dev/null @@ -1,22 +0,0 @@ -use rust_db_manager_core::infrastructure::repository::e_db_repository::EDBRepository; -use serde::{Deserialize, Serialize}; - -use super::dto_db_resources::DTODBResources; -use crate::infrastructure::db_assets::WebEDBRepository; - -#[derive(Clone, Serialize, Deserialize)] -pub struct DTODBServiceWebCategory { - pub category: String, - pub resources: DTODBResources -} - -impl DTODBServiceWebCategory { - - pub fn from(category: EDBRepository) -> DTODBServiceWebCategory { - DTODBServiceWebCategory { - category: category.to_string(), - resources: category.resources() - } - } - -} \ No newline at end of file diff --git a/src/infrastructure/dto/document/dto_document_data.rs b/src/infrastructure/dto/document/dto_document_data.rs new file mode 100644 index 0000000..607af68 --- /dev/null +++ b/src/infrastructure/dto/document/dto_document_data.rs @@ -0,0 +1,33 @@ +use rust_db_manager_core::domain::document::document_data::DocumentData; +use serde::Serialize; + +use super::dto_document_key::DTODocumentKey; + + +#[derive(Clone, Serialize)] +pub struct DTODocumentData { + data_base: String, + collection: String, + base_key: Option, + keys: Vec, + document: String +} + +impl DTODocumentData { + + pub fn from(document: &DocumentData) -> Self { + Self { + data_base: document.data_base(), + collection: document.collection(), + base_key: match document.base_key() { + Some(key) => Some(DTODocumentKey::from(&key)), + None => None, + }, + keys: document.keys().iter() + .map(|k| DTODocumentKey::from(k)) + .collect(), + document: document.document(), + } + } + +} \ No newline at end of file diff --git a/src/infrastructure/dto/document/dto_document_key.rs b/src/infrastructure/dto/document/dto_document_key.rs new file mode 100644 index 0000000..41b91ea --- /dev/null +++ b/src/infrastructure/dto/document/dto_document_key.rs @@ -0,0 +1,47 @@ +use rust_db_manager_core::domain::{document::{document_key::DocumentKey, document_key_attribute::DocumentKeyAttribute}, e_json_type::EJSONType}; +use serde::{Deserialize, Serialize}; + +use crate::commons::exception::api_exception::ApiException; + +use super::dto_document_key_attribute::DTODocumentKeyAttribute; + + +#[derive(Clone, Serialize, Deserialize)] +pub struct DTODocumentKey { + name: String, + value: String, + jtype: String, + attributes: Vec +} + +impl DTODocumentKey { + + pub fn from(key: &DocumentKey) -> Self { + Self { + name: key.name(), + value: key.value(), + jtype: key.jtype().to_string(), + attributes: key.attributes().iter() + .map(|a| DTODocumentKeyAttribute::from(a)) + .collect() + } + } + + pub fn from_dto(&self) -> Result { + let jstype = EJSONType::from_string(&self.jtype); + if let None = jstype { + let exception = ApiException::new(422, String::from("Field type not recognized.")); + return Err(exception); + } + + Ok(DocumentKey::new( + self.name.clone(), + self.value.clone(), + jstype.unwrap(), + self.attributes.iter() + .map(|a| DocumentKeyAttribute::new(a.key.clone(), a.value.clone())) + .collect() + )) + } + +} \ No newline at end of file diff --git a/src/infrastructure/dto/document/dto_document_key_attribute.rs b/src/infrastructure/dto/document/dto_document_key_attribute.rs new file mode 100644 index 0000000..29cb116 --- /dev/null +++ b/src/infrastructure/dto/document/dto_document_key_attribute.rs @@ -0,0 +1,20 @@ +use rust_db_manager_core::domain::document::document_key_attribute::DocumentKeyAttribute; +use serde::{Deserialize, Serialize}; + + +#[derive(Clone, Serialize, Deserialize)] +pub struct DTODocumentKeyAttribute { + pub key: String, + pub value: String +} + +impl DTODocumentKeyAttribute { + + pub fn from(attribute: &DocumentKeyAttribute) -> Self { + Self { + key: attribute.key(), + value: attribute.value() + } + } + +} \ No newline at end of file diff --git a/src/infrastructure/dto/document/dto_document_schema.rs b/src/infrastructure/dto/document/dto_document_schema.rs new file mode 100644 index 0000000..613fe2b --- /dev/null +++ b/src/infrastructure/dto/document/dto_document_schema.rs @@ -0,0 +1,25 @@ +use rust_db_manager_core::domain::document::document_schema::DocumentSchema; +use serde::Serialize; + +use crate::infrastructure::dto::field::generate::dto_field_data::DTOFieldData; + +#[derive(Clone, Serialize)] +pub struct DTODocumentSchema { + comments: Vec, + sw_strict: bool, + fields: Vec +} + +impl DTODocumentSchema { + + pub fn from(schema: &DocumentSchema) -> Self { + Self { + comments: schema.comments(), + sw_strict: schema.is_strict(), + fields: schema.fields().iter() + .map(|f| DTOFieldData::from(f)) + .collect() + } + } + +} \ No newline at end of file diff --git a/src/infrastructure/dto/dto_create_document.rs b/src/infrastructure/dto/dto_create_document.rs new file mode 100644 index 0000000..3dd0cf4 --- /dev/null +++ b/src/infrastructure/dto/dto_create_document.rs @@ -0,0 +1,8 @@ +use serde::Deserialize; + +use super::document::dto_document_key::DTODocumentKey; + +#[derive(Clone, Deserialize)] +pub struct DTOCreateDocument { + pub document: String +} \ No newline at end of file diff --git a/src/infrastructure/dto/dto_update_document.rs b/src/infrastructure/dto/dto_update_document.rs new file mode 100644 index 0000000..ccc4e96 --- /dev/null +++ b/src/infrastructure/dto/dto_update_document.rs @@ -0,0 +1,9 @@ +use serde::Deserialize; + +use super::document::dto_document_key::DTODocumentKey; + +#[derive(Clone, Deserialize)] +pub struct DTOUpdateDocument { + pub document: String, + pub keys: Vec, +} \ No newline at end of file diff --git a/src/infrastructure/dto/field/definition/dto_field_attribute_default_definition.rs b/src/infrastructure/dto/field/definition/dto_field_attribute_default_definition.rs new file mode 100644 index 0000000..2de520b --- /dev/null +++ b/src/infrastructure/dto/field/definition/dto_field_attribute_default_definition.rs @@ -0,0 +1,19 @@ +use rust_db_manager_core::domain::field::definition::field_attribute_default_definition::FieldAttributeDefaultDefinition; +use serde::Serialize; + +#[derive(Clone, Serialize)] +pub struct DTOFieldAttributeDefaultDefinition { + key: String, + value: String +} + +impl DTOFieldAttributeDefaultDefinition { + + pub fn from(default: &FieldAttributeDefaultDefinition) -> Self { + Self { + key: default.key(), + value: default.value() + } + } + +} \ No newline at end of file diff --git a/src/infrastructure/dto/field/definition/dto_field_attribute_definition.rs b/src/infrastructure/dto/field/definition/dto_field_attribute_definition.rs new file mode 100644 index 0000000..a3acea2 --- /dev/null +++ b/src/infrastructure/dto/field/definition/dto_field_attribute_definition.rs @@ -0,0 +1,25 @@ +use rust_db_manager_core::domain::field::definition::field_attribute_definition::FieldAttributeDefinition; +use serde::Serialize; + +use super::dto_field_attribute_default_definition::DTOFieldAttributeDefaultDefinition; + +#[derive(Clone, Serialize)] +pub struct DTOFieldAttributeDefinition { + name: String, + code: String, + values: Vec, +} + +impl DTOFieldAttributeDefinition { + + pub fn from(attribute: &FieldAttributeDefinition) -> Self { + Self { + name: attribute.name(), + code: attribute.code(), + values: attribute.values().iter() + .map(|a| DTOFieldAttributeDefaultDefinition::from(a)) + .collect() + } + } + +} \ No newline at end of file diff --git a/src/infrastructure/dto/field/definition/dto_field_definition.rs b/src/infrastructure/dto/field/definition/dto_field_definition.rs new file mode 100644 index 0000000..9216d6d --- /dev/null +++ b/src/infrastructure/dto/field/definition/dto_field_definition.rs @@ -0,0 +1,31 @@ +use rust_db_manager_core::domain::field::definition::field_definition::FieldDefinition; +use serde::Serialize; + +use super::dto_field_attribute_definition::DTOFieldAttributeDefinition; + +#[derive(Clone, Serialize)] +pub struct DTOFieldDefinition { + order: usize, + name: String, + code: String, + swsize: bool, + multiple: bool, + attributes: Vec +} + +impl DTOFieldDefinition { + + pub fn from(definition: &FieldDefinition) -> Self { + Self { + order: definition.order(), + name: definition.name(), + code: definition.code().to_string(), + swsize: definition.swsize(), + multiple: definition.multiple(), + attributes: definition.attributes().iter() + .map(|a| DTOFieldAttributeDefinition::from(a)) + .collect() + } + } + +} \ No newline at end of file diff --git a/src/infrastructure/dto/field/generate/dto_field_atribute.rs b/src/infrastructure/dto/field/generate/dto_field_atribute.rs new file mode 100644 index 0000000..95fd9fd --- /dev/null +++ b/src/infrastructure/dto/field/generate/dto_field_atribute.rs @@ -0,0 +1,23 @@ +use rust_db_manager_core::domain::field::generate::field_attribute::FieldAttribute; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Serialize, Deserialize)] +pub struct DTOFieldAttribute { + key: String, + value: String, +} + +impl DTOFieldAttribute { + + pub fn from(attribute: &FieldAttribute) -> Self { + Self { + key: attribute.key(), + value: attribute.value() + } + } + + pub fn from_dto(&self) -> FieldAttribute { + FieldAttribute::new(self.key.clone(), self.value.clone()) + } + +} \ No newline at end of file diff --git a/src/infrastructure/dto/field/generate/dto_field_data.rs b/src/infrastructure/dto/field/generate/dto_field_data.rs new file mode 100644 index 0000000..f614dc6 --- /dev/null +++ b/src/infrastructure/dto/field/generate/dto_field_data.rs @@ -0,0 +1,61 @@ +use rust_db_manager_core::domain::field::{e_field_code::EFieldCode, generate::field_data::FieldData}; +use serde::{Deserialize, Serialize}; + +use crate::commons::exception::api_exception::ApiException; + +use super::{dto_field_atribute::DTOFieldAttribute, dto_field_reference::DTOFieldReference}; + +#[derive(Clone, Serialize, Deserialize)] +pub struct DTOFieldData { + order: i32, + code: String, + value: String, + swsize: bool, + size: i32, + mutable: bool, + attributes: Vec, + reference: Vec +} + +impl DTOFieldData { + + pub fn from(field: &FieldData) -> Self { + Self { + order: field.order(), + code: field.code().to_string(), + value: field.value(), + swsize: field.is_resize(), + size: field.size(), + mutable: field.is_mutable(), + attributes: field.attributes().iter() + .map(|a| DTOFieldAttribute::from(a)) + .collect(), + reference: field.reference().iter() + .map(|r| DTOFieldReference::from(r)) + .collect() + } + } + + pub fn from_dto(&self) -> Result { + let code = EFieldCode::from_string(&self.code); + if let None = code { + let exception = ApiException::new(422, String::from("Field code not recognized.")); + return Err(exception); + } + + let attributes = self.attributes.iter() + .map(|a| a.from_dto()) + .collect(); + + let reference = self.reference.iter() + .map(|a| a.from_dto()) + .collect(); + + Ok(FieldData::new( + self.order, code.unwrap(), self.value.clone(), + self.swsize, self.size, self.mutable, + attributes, reference + )) + } + +} \ No newline at end of file diff --git a/src/infrastructure/dto/field/generate/dto_field_reference.rs b/src/infrastructure/dto/field/generate/dto_field_reference.rs new file mode 100644 index 0000000..425d12e --- /dev/null +++ b/src/infrastructure/dto/field/generate/dto_field_reference.rs @@ -0,0 +1,23 @@ +use rust_db_manager_core::domain::field::generate::field_reference::FieldReference; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Serialize, Deserialize)] +pub struct DTOFieldReference { + collection: String, + field: String +} + +impl DTOFieldReference { + + pub fn from(reference: &FieldReference) -> Self { + Self { + collection: reference.collection(), + field: reference.field() + } + } + + pub fn from_dto(&self) -> FieldReference { + FieldReference::new(self.collection.clone(), self.field.clone()) + } + +} \ No newline at end of file diff --git a/src/infrastructure/dto/pagination/dto_query_pagination.rs b/src/infrastructure/dto/pagination/dto_query_pagination.rs index cf07616..9d71465 100644 --- a/src/infrastructure/dto/pagination/dto_query_pagination.rs +++ b/src/infrastructure/dto/pagination/dto_query_pagination.rs @@ -2,17 +2,17 @@ use serde::Deserialize; #[derive(Debug, Deserialize)] pub struct DTOQueryPagination { - #[serde(default = "default_offset")] - pub offset: usize, #[serde(default = "default_limit")] pub limit: usize, + #[serde(default = "default_offset")] + pub offset: usize, } -fn default_offset() -> usize { +fn default_limit() -> usize { 0 } -fn default_limit() -> usize { +fn default_offset() -> usize { 10 } diff --git a/src/infrastructure/dto/service/definition/dto_service.rs b/src/infrastructure/dto/service/definition/dto_service.rs new file mode 100644 index 0000000..8d2440f --- /dev/null +++ b/src/infrastructure/dto/service/definition/dto_service.rs @@ -0,0 +1,27 @@ +use rust_db_manager_core::infrastructure::db_service::DBService; +use serde::Serialize; + +use super::dto_service_category::DTOServiceCategory; + +#[derive(Clone, Serialize)] +pub struct DTOService { + pub name: String, + pub owner: String, + pub protected: bool, + pub timestamp: u128, + pub connection_data: DTOServiceCategory, +} + +impl DTOService { + + pub fn from(service: DBService) -> Self { + Self { + name: service.name(), + owner: service.owner(), + protected: service.is_protected(), + timestamp: service.timestamp(), + connection_data: DTOServiceCategory::from(service.connection_data()) + } + } + +} \ No newline at end of file diff --git a/src/infrastructure/dto/service/definition/dto_service_category.rs b/src/infrastructure/dto/service/definition/dto_service_category.rs new file mode 100644 index 0000000..b3028c0 --- /dev/null +++ b/src/infrastructure/dto/service/definition/dto_service_category.rs @@ -0,0 +1,25 @@ +use rust_db_manager_core::domain::connection_data::ConnectionData; +use serde::Serialize; + +use crate::infrastructure::db_assets::WebEDBRepository; + +use super::dto_service_resources::DTOServiceResources; + +#[derive(Clone, Serialize)] +pub struct DTOServiceCategory { + pub category: String, + pub connection: String, + pub resources: DTOServiceResources +} + +impl DTOServiceCategory { + + pub fn from(connection: ConnectionData) -> Self { + Self { + category: connection.category().to_string(), + connection: connection.connection(), + resources: connection.category().resources() + } + } + +} \ No newline at end of file diff --git a/src/infrastructure/dto/service/definition/dto_service_category_lite.rs b/src/infrastructure/dto/service/definition/dto_service_category_lite.rs new file mode 100644 index 0000000..16313d1 --- /dev/null +++ b/src/infrastructure/dto/service/definition/dto_service_category_lite.rs @@ -0,0 +1,23 @@ +use rust_db_manager_core::infrastructure::repository::e_db_repository::EDBRepository; +use serde::Serialize; + +use crate::infrastructure::db_assets::WebEDBRepository; + +use super::dto_service_resources::DTOServiceResources; + +#[derive(Clone, Serialize)] +pub struct DTOServiceCategoryLite { + pub category: String, + pub resources: DTOServiceResources +} + +impl DTOServiceCategoryLite { + + pub fn from(category: &EDBRepository) -> Self { + Self { + category: category.to_string(), + resources: category.resources() + } + } + +} \ No newline at end of file diff --git a/src/infrastructure/dto/service/definition/dto_service_lite.rs b/src/infrastructure/dto/service/definition/dto_service_lite.rs new file mode 100644 index 0000000..5e61d16 --- /dev/null +++ b/src/infrastructure/dto/service/definition/dto_service_lite.rs @@ -0,0 +1,21 @@ +use rust_db_manager_core::infrastructure::db_service_lite::DBServiceLite; +use serde::Serialize; + +use super::dto_service_category_lite::DTOServiceCategoryLite; + +#[derive(Clone, Serialize)] +pub struct DTOServiceLite { + pub name: String, + pub category: DTOServiceCategoryLite +} + +impl DTOServiceLite { + + pub fn from(service: &DBServiceLite) -> Self { + Self { + name: service.name(), + category: DTOServiceCategoryLite::from(&service.category()) + } + } + +} \ No newline at end of file diff --git a/src/infrastructure/dto/db_service/dto_db_resources.rs b/src/infrastructure/dto/service/definition/dto_service_resources.rs similarity index 74% rename from src/infrastructure/dto/db_service/dto_db_resources.rs rename to src/infrastructure/dto/service/definition/dto_service_resources.rs index 234c824..3919e9f 100644 --- a/src/infrastructure/dto/db_service/dto_db_resources.rs +++ b/src/infrastructure/dto/service/definition/dto_service_resources.rs @@ -1,16 +1,16 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Serialize, Deserialize)] -pub struct DTODBResources { +pub struct DTOServiceResources { pub web_site: String, pub color: String, pub image: String } -impl DTODBResources { +impl DTOServiceResources { - pub fn new(web_site: String, color: String, image: String) -> DTODBResources { - DTODBResources { + pub fn new(web_site: String, color: String, image: String) -> Self { + Self { web_site, color, image } } diff --git a/src/infrastructure/dto/db_service/dto_db_connection_data.rs b/src/infrastructure/dto/service/generate/dto_db_connection_data.rs similarity index 76% rename from src/infrastructure/dto/db_service/dto_db_connection_data.rs rename to src/infrastructure/dto/service/generate/dto_db_connection_data.rs index 928f281..5969d4e 100644 --- a/src/infrastructure/dto/db_service/dto_db_connection_data.rs +++ b/src/infrastructure/dto/service/generate/dto_db_connection_data.rs @@ -1,7 +1,7 @@ use serde::Deserialize; #[derive(Clone, Deserialize)] -pub struct DTOConnectionData { +pub struct DTODBConnectionData { pub category: String, pub connection: String } \ No newline at end of file diff --git a/src/infrastructure/dto/service/generate/dto_service_create_request.rs b/src/infrastructure/dto/service/generate/dto_service_create_request.rs new file mode 100644 index 0000000..e20b8f4 --- /dev/null +++ b/src/infrastructure/dto/service/generate/dto_service_create_request.rs @@ -0,0 +1,12 @@ +use serde::Deserialize; + +use super::dto_db_connection_data::DTODBConnectionData; + +#[derive(Clone, Deserialize)] +pub struct DTOServiceRequest { + pub name: String, + pub owner: String, + pub protected: bool, + pub password: String, + pub connection_data: DTODBConnectionData +} \ No newline at end of file diff --git a/src/infrastructure/dto/service/generate/dto_service_suscribe_request.rs b/src/infrastructure/dto/service/generate/dto_service_suscribe_request.rs new file mode 100644 index 0000000..600c660 --- /dev/null +++ b/src/infrastructure/dto/service/generate/dto_service_suscribe_request.rs @@ -0,0 +1,8 @@ +use serde::Deserialize; + +#[derive(Clone, Deserialize)] +pub struct DTOServiceSuscribeRequest { + pub name: String, + pub password: String, + pub owner: String, +} \ No newline at end of file diff --git a/src/infrastructure/dto/table/dto_table_data_field.rs b/src/infrastructure/dto/table/dto_table_data_field.rs new file mode 100644 index 0000000..7122f7d --- /dev/null +++ b/src/infrastructure/dto/table/dto_table_data_field.rs @@ -0,0 +1,23 @@ +use rust_db_manager_core::domain::table::table_data_field::TableDataField; +use serde::Serialize; + +#[derive(Clone, Serialize)] +pub struct DTOTableDataField { + order: usize, + name: String, + value: String, + json_type: String, +} + +impl DTOTableDataField { + + pub fn from(data: &TableDataField) -> Self { + Self { + order: data.order(), + name: data.name(), + value: data.value(), + json_type: data.json_type(), + } + } + +} \ No newline at end of file diff --git a/src/infrastructure/dto/table/dto_table_data_group.rs b/src/infrastructure/dto/table/dto_table_data_group.rs new file mode 100644 index 0000000..6050ff3 --- /dev/null +++ b/src/infrastructure/dto/table/dto_table_data_group.rs @@ -0,0 +1,25 @@ +use rust_db_manager_core::domain::table::table_data_group::TableDataGroup; +use serde::Serialize; + +use super::dto_table_data_field::DTOTableDataField; + +#[derive(Clone, Serialize)] +pub struct DTOTableDataGroup { + order: usize, + name: String, + fields: Vec, +} + +impl DTOTableDataGroup { + + pub fn from(data: &TableDataGroup) -> Self { + Self { + order: data.order(), + name: data.name(), + fields: data.fields().iter() + .map(|f| DTOTableDataField::from(f)) + .collect() + } + } + +} \ No newline at end of file diff --git a/src/infrastructure/handler.rs b/src/infrastructure/handler.rs new file mode 100644 index 0000000..31511bf --- /dev/null +++ b/src/infrastructure/handler.rs @@ -0,0 +1,51 @@ +use std::collections::HashMap; + +use axum::{extract::{Path, Request}, http::{HeaderMap, StatusCode}, middleware::Next, response::{IntoResponse, Response}}; +use rust_db_manager_core::commons::configuration::configuration::Configuration; + +use crate::commons::exception::api_exception::ApiException; + +use super::{services_jwt::ServicesJWT, utils::find_token}; + +pub(crate) async fn autentication_handler(headers: HeaderMap, Path(params): Path>, request: Request, next: Next) -> Result { + let service = params.get("service"); + if service.is_none() { + let exception = ApiException::new(StatusCode::NOT_FOUND.as_u16(), String::from("Service not defined.")); + return Err(exception.into_response()); + } + + let o_service = Configuration::find_service(&service.unwrap()); + if o_service.is_none() { + let exception = ApiException::new(StatusCode::NOT_FOUND.as_u16(), String::from("Service not found.")); + return Err(exception.into_response()); + } + + let service = o_service.unwrap(); + if service.is_protected() { + let o_token = find_token(headers); + if let Err(exception) = o_token { + return Err(exception.into_response()); + } + + let token = o_token.unwrap(); + if token.is_none() { + let exception = ApiException::new(StatusCode::UNAUTHORIZED.as_u16(), String::from("Token not found")); + return Err(exception.into_response()); + } + + let result = ServicesJWT::verify(&token.unwrap().value); + if let Err(exception) = result { + return Err(exception.into_response()); + } + + let services = result.unwrap(); + + let authorized = services.iter().find(|s| s.is_same(service.clone())); + if authorized.is_none() { + let error = ApiException::new(StatusCode::UNAUTHORIZED.as_u16(), String::from("Token not found")); + return Err(error.into_response()); + } + } + + return Ok(next.run(request).await); +} \ No newline at end of file diff --git a/src/infrastructure/pagination.rs b/src/infrastructure/pagination.rs index 3aefc1c..f2e0283 100644 --- a/src/infrastructure/pagination.rs +++ b/src/infrastructure/pagination.rs @@ -6,48 +6,48 @@ impl Pagination { pub fn paginate(params: DTOQueryPagination, collection: Vec) -> DTOPaginatedCollection { let size = collection.len(); - let offset = params.offset; let limit = params.limit; + let offset = params.offset; - if offset >= size { - let previous = Pagination::calculate_previous(size, offset, limit); + if limit >= size { + let previous = Pagination::calculate_previous(size, limit, offset); return DTOPaginatedCollection::new(size, previous, size, Vec::new()); } - if size == 0 || limit == 0 { - return DTOPaginatedCollection::new(size, offset, offset, Vec::new()); + if size == 0 || offset == 0 { + return DTOPaginatedCollection::new(size, limit, limit, Vec::new()); } - let mut limit_fixed = limit; - if offset + limit >= size { - limit_fixed = size - offset; + let mut offset_fixed = offset; + if limit + offset >= size { + offset_fixed = size - limit; } - let cursor = offset + limit_fixed; + let cursor = limit + offset_fixed; - let next = Pagination::calculate_next(size, cursor, limit); - let previous = Pagination::calculate_previous(size, cursor, limit); + let next = Pagination::calculate_next(size, cursor, offset); + let previous = Pagination::calculate_previous(size, cursor, offset); - let slice = collection[offset..=cursor-1].to_vec(); + let slice = collection[limit..=cursor-1].to_vec(); return DTOPaginatedCollection::new(size, previous, next, slice); } - fn calculate_next(size: usize, cursor: usize, limit: usize) -> usize { - let next = cursor + limit; + fn calculate_next(size: usize, cursor: usize, offset: usize) -> usize { + let next = cursor + offset; if next >= size { return size; } return next; } - fn calculate_previous(size: usize, cursor: usize, limit: usize) -> usize { - if cursor.checked_sub(limit).is_none() { + fn calculate_previous(size: usize, cursor: usize, offset: usize) -> usize { + if cursor.checked_sub(offset).is_none() { return 0; } - let previous = cursor - limit; + let previous = cursor - offset; if previous > size { - return Pagination::calculate_previous(size, size, limit); + return Pagination::calculate_previous(size, size, offset); } return previous; } diff --git a/src/infrastructure/services_jwt.rs b/src/infrastructure/services_jwt.rs new file mode 100644 index 0000000..fb855c0 --- /dev/null +++ b/src/infrastructure/services_jwt.rs @@ -0,0 +1,156 @@ +use hmac::{Hmac, Mac}; +use jwt::{SignWithKey, VerifyWithKey}; +use sha2::Sha256; +use std::collections::BTreeMap; + +use rust_db_manager_core::{commons::configuration::configuration::Configuration, infrastructure::db_service::DBService}; + +use crate::{commons::{configuration::web_configuration::WebConfiguration, exception::auth_exception::AuthException}, domain::cookie::cookie::Cookie}; + +pub struct ServicesJWT { + +} + +impl ServicesJWT { + + pub fn sign(service: &DBService) -> Result { + Self::sign_services(Vec::from(vec![service.clone()])) + } + + pub fn sign_empty() -> Result { + Self::sign_services(Vec::new()) + } + + pub fn sign_services(services: Vec) -> Result { + let s_key = services.iter() + .map(|s| s.salt()) + .collect::>() + .join("#"); + + let key: Result, hmac::digest::InvalidLength> = Hmac::new_from_slice(s_key.as_bytes()); + if key.is_err() { + let exception = AuthException::new_reset(500, key.unwrap_err().to_string()); + return Err(exception); + } + + let collection = services.iter() + .map(|s| s.name()) + .collect::>() + .join("-"); + + let mut claims = BTreeMap::new(); + claims.insert("sub", collection); + + let token_str = claims.sign_with_key(&key.unwrap()); + if token_str.is_err() { + let exception = AuthException::new_reset(500, token_str.unwrap_err().to_string()); + return Err(exception); + } + + Ok(Self::default_cookie(token_str.unwrap())) + } + + pub fn update(token: &str, service: &DBService) -> Result { + let _ = Self::verify(token)?; + + let mut services = Self::find_services(token)?; + if services.iter().find(|s| s.name() == service.name()).is_some() { + let exception = AuthException::new(500, String::from("This token is already subscribed to the service.")); + return Err(exception); + } + + services.push(service.clone()); + + Ok(Self::sign_services(services)?) + } + + pub fn remove(token: &str, service: &DBService) -> Result { + let _ = Self::verify(token)?; + + let mut services = Self::find_services(token)?; + if let Some(position) = services.iter().position(|s| s.name() == service.name()) { + services.remove(position); + } + + Ok(Self::sign_services(services)?) + } + + pub fn verify(token: &str) -> Result, AuthException> { + let services = Self::find_services(token)?; + let salt = services.iter() + .map(|s| s.salt()) + .collect::>() + .join("#"); + + let key: Result, hmac::digest::InvalidLength> = Hmac::new_from_slice(salt.as_bytes()); + if key.is_err() { + let exception = AuthException::new_reset(500, key.unwrap_err().to_string()); + return Err(exception); + } + + let result: Result, jwt::Error> = token.verify_with_key(&key.unwrap()); + if result.is_err() { + let exception = AuthException::new_reset(500, result.unwrap_err().to_string()); + return Err(exception); + } + + Ok(services) + } + + fn find_services(token: &str) -> Result, AuthException> { + let fragments = token.split(".").collect::>(); + if fragments.len() != 3 { + let exception = AuthException::new_reset(401, String::from("Invalid token.")); + return Err(exception); + } + + let claims = fragments.get(1).unwrap().trim(); + let b_services = base64::decode_config(claims, base64::URL_SAFE_NO_PAD); + if b_services.is_err() { + let exception = AuthException::new_reset(500, b_services.unwrap_err().to_string()); + return Err(exception); + } + + let s_services = String::from_utf8(b_services.unwrap()); + if s_services.is_err() { + let exception = AuthException::new_reset(500, s_services.unwrap_err().to_string()); + return Err(exception); + } + + let m_services: Result, serde_json::Error> = serde_json::from_str(&s_services.unwrap()); + if m_services.is_err() { + let exception = AuthException::new_reset(500, m_services.unwrap_err().to_string()); + return Err(exception); + } + + let b_m_services = m_services.unwrap(); + let v_services = b_m_services.get("sub"); + + if v_services.is_none() { + let exception = AuthException::new_reset(500, String::from("No services found.")); + return Err(exception); + } + + let mut collection = Vec::new(); + + for s_service in v_services.unwrap().split("-").filter(|s| !s.is_empty()).collect::>() { + let service = Configuration::find_service(s_service); + if service.is_none() { + let exception = AuthException::new_reset(500, String::from("Unknown service.")); + return Err(exception); + } + collection.push(service.unwrap()); + } + + Ok(collection) + } + + fn default_cookie(token: String) -> Cookie { + let mut cookie = Cookie::new(String::from(WebConfiguration::COOKIE_NAME), token); + cookie.path = Some(String::from("/")); + cookie.http_only = Some(true); + + cookie + } + +} \ No newline at end of file diff --git a/src/infrastructure/utils.rs b/src/infrastructure/utils.rs index 087845a..e87db73 100644 --- a/src/infrastructure/utils.rs +++ b/src/infrastructure/utils.rs @@ -1,6 +1,8 @@ -use axum::{body::Body, http::Response, response::IntoResponse}; +use axum::{body::Body, http::{header::{COOKIE, SET_COOKIE}, HeaderMap, Response, StatusCode}, response::IntoResponse}; -use crate::commons::exception::api_exception::ApiException; +use crate::{commons::{configuration::web_configuration::WebConfiguration, exception::{api_exception::ApiException, auth_exception::AuthException}}, domain::cookie::{cookie::Cookie, jar::Jar}}; + +use super::services_jwt::ServicesJWT; impl IntoResponse for ApiException { @@ -11,4 +13,49 @@ impl IntoResponse for ApiException { .unwrap() } +} + +impl IntoResponse for AuthException { + + fn into_response(self) -> Response { + let mut builder = Response::builder(); + if let Ok(cookie) = ServicesJWT::sign_empty() { + if self.reset() { + builder = builder.header(SET_COOKIE, cookie.to_string()); + } + } + + builder + .status(self.status()) + .body(Body::from(self.message())) + .unwrap() + } + +} + +pub(crate) fn find_token(headers: HeaderMap) -> Result, AuthException> { + let o_cookies = headers.get(COOKIE); + if o_cookies.is_none() { + return Ok(None); + } + + let cookies = o_cookies.unwrap().to_str(); + if cookies.is_err() { + let exception = AuthException::new_reset(StatusCode::UNAUTHORIZED.as_u16(), String::from("Token has non valid format")); + return Err(exception); + } + + let jar = Jar::from_string(cookies.unwrap()); + if jar.is_err() { + return Err(jar.unwrap_err()); + } + + Ok(jar.unwrap().find(WebConfiguration::COOKIE_NAME)) +} + +pub(crate) fn not_found() -> Response { + let error = ApiException::new( + StatusCode::NOT_FOUND.as_u16(), + String::from("Not found")); + return error.into_response(); } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 92a1758..8bb2381 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,29 +4,83 @@ pub mod commons { } pub mod exception { pub mod api_exception; + pub mod auth_exception; } } pub mod domain { pub mod builder_db_connection_data; pub mod builder_db_service; + pub mod cookie { + pub mod builder_cookie; + pub mod builder_jar; + pub mod cookie; + pub mod jar; + pub mod same_site; + } } pub mod infrastructure { pub mod dto { - pub mod db_service { - pub mod dto_db_connection_data; - pub mod dto_db_resources; - pub mod dto_db_service_web_category; - pub mod dto_db_service_lite; - pub mod dto_db_service; + pub mod collection { + pub mod dto_collection_data; + pub mod dto_generate_collection_query; + pub mod dto_collection_definition; + pub mod dto_rename_collection_query; + } + pub mod data_base { + pub mod dto_generate_data_base_query; + } + pub mod document { + pub mod dto_document_data; + pub mod dto_document_key_attribute; + pub mod dto_document_key; + pub mod dto_document_schema; + } + pub mod field { + pub mod definition { + pub mod dto_field_attribute_default_definition; + pub mod dto_field_attribute_definition; + pub mod dto_field_definition; + } + pub mod generate { + pub mod dto_field_atribute; + pub mod dto_field_data; + pub mod dto_field_reference; + } } pub mod pagination { pub mod dto_paginated_collection; pub mod dto_query_pagination; } + pub mod service { + pub mod definition { + pub mod dto_service_category_lite; + pub mod dto_service_category; + pub mod dto_service_lite; + pub mod dto_service_resources; + pub mod dto_service; + } + pub mod generate { + pub mod dto_db_connection_data; + pub mod dto_service_create_request; + pub mod dto_service_suscribe_request; + } + } + pub mod table { + pub mod dto_table_data_field; + pub mod dto_table_data_group; + } + pub mod dto_create_document; pub mod dto_server_status; + pub mod dto_update_document; } - pub mod controller; + pub mod controller_collection; + pub mod controller_database; + pub mod controller_document; + pub mod controller_server; + pub mod controller_service; pub mod db_assets; + pub mod handler; pub mod pagination; + pub mod services_jwt; pub mod utils; } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index c7cc543..4b36d76 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,17 +3,21 @@ use std::net::SocketAddr; use tower_http::cors::CorsLayer; use axum::Router; -use rust_db_manager_api::{commons::configuration::web_configuration::WebConfiguration, infrastructure::controller::Controller}; +use rust_db_manager_api::{commons::configuration::web_configuration::WebConfiguration, infrastructure::{controller_collection::ControllerCollection, controller_database::ControllerDataBase, controller_document::ControllerDocument, controller_server::ControllerServer, controller_service::ControllerService}}; #[tokio::main] async fn main() { let _ = WebConfiguration::initialize(); - - let router = Router::new(); - let app = Controller::route(router) - .layer(CorsLayer::permissive()) + + let app = Router::new() + .merge(ControllerServer::route(Router::new())) + .merge(ControllerService::route(Router::new())) + .merge(ControllerDataBase::route(Router::new())) + .merge(ControllerCollection::route(Router::new())) + .merge(ControllerDocument::route(Router::new())) + .layer(CorsLayer::very_permissive()) .into_make_service_with_connect_info::(); let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); axum::serve(listener, app).await.unwrap(); -} +} \ No newline at end of file