From 58336dca49121d732cc2c2d8c1c17877444d5dfa Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Thu, 23 Nov 2023 21:36:17 +0200
Subject: [PATCH 001/284] stack form field fix

---
 src/forms/stack.rs                 | 57 +++++++++++++++++++++++++++---
 src/routes/stack/add.rs            | 11 ++++--
 src/routes/stack/compose.rs        |  4 +--
 src/routes/stack/deploy.rs         |  2 +-
 src/startup.rs                     |  3 +-
 tests/custom-stack-payload-11.json |  2 ++
 6 files changed, 67 insertions(+), 12 deletions(-)
 create mode 100644 tests/custom-stack-payload-11.json

diff --git a/src/forms/stack.rs b/src/forms/stack.rs
index 78a0847b..c3f108a1 100644
--- a/src/forms/stack.rs
+++ b/src/forms/stack.rs
@@ -36,8 +36,14 @@ pub struct Ports {
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct DockerImage {
+    #[validate(min_length=3)]
+    #[validate(max_length=50)]
     pub dockerhub_user: Option<String>,
+    #[validate(min_length=3)]
+    #[validate(max_length=50)]
     pub dockerhub_name: Option<String>,
+    #[validate(min_length=3)]
+    #[validate(max_length=100)]
     pub dockerhub_image: Option<String>,
 }
 
@@ -64,24 +70,46 @@ impl AsRef<DockerImage> for App {
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct StackForm {
+    #[validate(min_length=2)]
+    #[validate(max_length=255)]
     #[serde(rename= "commonDomain")]
     pub common_domain: Option<String>,
     pub domain_list: Option<DomainList>,
+    #[validate(min_length=2)]
+    #[validate(max_length=255)]
     pub stack_code: Option<String>,
+    #[validate(min_length=2)]
+    #[validate(max_length=50)]
     pub region: String,
+    #[validate(min_length=2)]
+    #[validate(max_length=50)]
     pub zone: Option<String>,
+    #[validate(min_length=2)]
+    #[validate(max_length=50)]
     pub server: String,
+    #[validate(min_length=2)]
+    #[validate(max_length=50)]
     pub os: String,
+    #[validate(min_length=3)]
+    #[validate(max_length=50)]
     pub ssl: String,
     pub vars: Option<Vec<Var>>,
     pub integrated_features: Option<Vec<Value>>,
     pub extended_features: Option<Vec<Value>>,
     pub subscriptions: Option<Vec<String>>,
     pub form_app: Option<Vec<String>>,
+    #[validate(min_length=3)]
+    #[validate(max_length=50)]
     pub disk_type: Option<String>,
     pub save_token: bool,
+    #[validate(min_length=10)]
+    #[validate(max_length=255)]
     pub cloud_token: String,
+    #[validate(min_length=2)]
+    #[validate(max_length=50)]
     pub provider: String,
+    #[validate(min_length=3)]
+    #[validate(max_length=50)]
     pub selected_plan: String,
     pub custom: Custom,
 }
@@ -142,10 +170,16 @@ pub struct Custom {
     pub feature: Option<Vec<Feature>>,
     pub service: Option<Vec<Service>>,
     #[serde(rename = "servers_count")]
+    #[validate(minimum = 0)]
+    #[validate(maximum = 10)]
     pub servers_count: u32,
+    #[validate(min_length=3)]
+    #[validate(max_length=50)]
     #[serde(rename = "custom_stack_code")]
     pub custom_stack_code: String,
     #[serde(rename = "project_git_url")]
+    #[validate(min_length=3)]
+    #[validate(max_length=255)]
     pub project_git_url: Option<String>,
     #[serde(rename = "custom_stack_category")]
     pub custom_stack_category: Option<Vec<String>>,
@@ -154,6 +188,8 @@ pub struct Custom {
     #[serde(rename = "custom_stack_description")]
     pub custom_stack_description: Option<String>,
     #[serde(rename = "project_name")]
+    #[validate(min_length=3)]
+    #[validate(max_length=255)]
     pub project_name: String,
     #[serde(rename = "project_overview")]
     pub project_overview: Option<String>,
@@ -164,6 +200,8 @@ pub struct Custom {
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct App {
     #[serde(rename = "_etag")]
+    #[validate(min_length=3)]
+    #[validate(max_length=255)]
     pub etag: Option<String>,
     #[serde(rename = "_id")]
     pub id: u32,
@@ -171,8 +209,14 @@ pub struct App {
     pub created: Option<String>,
     #[serde(rename = "_updated")]
     pub updated: Option<String>,
+    #[validate(min_length=3)]
+    #[validate(max_length=50)]
     pub name: String,
+    #[validate(min_length=3)]
+    #[validate(max_length=50)]
     pub code: String,
+    #[validate(min_length=3)]
+    #[validate(max_length=50)]
     #[serde(rename = "type")]
     pub type_field: String,
     #[serde(flatten)]
@@ -185,6 +229,7 @@ pub struct App {
     pub docker_image: DockerImage,
     #[serde(flatten)]
     pub requirements: Requirements,
+    #[validate(minimum=1)]
     pub popularity: Option<u32>,
     pub commercial: Option<bool>,
     pub subscription: Option<Value>,
@@ -195,7 +240,6 @@ pub struct App {
     pub price: Option<Price>,
     pub icon: Option<Icon>,
     pub domain: Option<String>,
-    pub main: bool,
     pub category_id: Option<u32>,
     pub parent_app_id: Option<u32>,
     pub descr: Option<String>,
@@ -213,6 +257,7 @@ pub struct Web {
     #[serde(flatten)]
     pub app: App,
     pub custom: Option<bool>,
+    pub main: bool,
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
@@ -248,8 +293,7 @@ pub struct IconLight {
 pub struct IconDark {
 }
 
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Version {
     #[serde(rename = "_etag")]
     pub etag: Option<String>,
@@ -259,12 +303,15 @@ pub struct Version {
     pub created: Option<String>,
     #[serde(rename = "_updated")]
     pub updated: Option<String>,
-    #[serde(rename = "app_id")]
-    pub app_id: u32,
+    pub app_id: Option<u32>,
     pub name: String,
+    #[validate(min_length=3)]
+    #[validate(max_length=20)]
     pub version: String,
     #[serde(rename = "update_status")]
     pub update_status: Option<String>,
+    #[validate(min_length=3)]
+    #[validate(max_length=20)]
     pub tag: String,
 }
 
diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs
index 27260e04..4f08fe59 100644
--- a/src/routes/stack/add.rs
+++ b/src/routes/stack/add.rs
@@ -11,9 +11,10 @@ use chrono::Utc;
 use serde_json::Value;
 use sqlx::PgPool;
 use std::str;
+use serde_valid::json::FromJsonValue;
+use serde_valid::Validate;
 use tracing::Instrument;
 use uuid::Uuid;
-use crate::models;
 
 
 #[tracing::instrument(name = "Add stack.")]
@@ -32,7 +33,7 @@ pub async fn add(
         }
         Err(_err) => {
             let msg = format!("Invalid data. {:?}", _err);
-            return JsonResponse::<StackForm>::build().err("Invalid data".to_owned());
+            return JsonResponse::<StackForm>::build().err(msg);
         }
     };
 
@@ -57,6 +58,10 @@ pub async fn add(
     let query_span = tracing::info_span!("Saving new stack details into the database");
 
     let stack_name = form.custom.custom_stack_code.clone();
+
+
+    let errors = form.validate().is_ok();
+    println!("{:?}",errors);
     let body: Value = match serde_json::to_value::<StackForm>(form) {
         Ok(body) => body,
         Err(err) => {
@@ -65,7 +70,7 @@ pub async fn add(
         }
     };
 
-    return match sqlx::query!(
+    match sqlx::query!(
         r#"
         INSERT INTO user_stack (stack_id, user_id, name, body, created_at, updated_at)
         VALUES ($1, $2, $3, $4, $5, $6)
diff --git a/src/routes/stack/compose.rs b/src/routes/stack/compose.rs
index 4d19a3f5..4de94f84 100644
--- a/src/routes/stack/compose.rs
+++ b/src/routes/stack/compose.rs
@@ -51,7 +51,7 @@ pub async fn add(
     match stack {
         Some(stack) => {
             let id = stack.id.clone();
-            let mut dc = DcBuilder::new(stack);
+            let dc = DcBuilder::new(stack);
             let fc = dc.build();
             tracing::debug!("Docker compose file content {:?}", fc);
 
@@ -104,7 +104,7 @@ pub async fn admin(
     match stack {
         Some(stack) => {
             let id = stack.id.clone();
-            let mut dc = DcBuilder::new(stack);
+            let dc = DcBuilder::new(stack);
             let fc = dc.build();
             // tracing::debug!("Docker compose file content {:?}", fc);
             return JsonResponse::build()
diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs
index 45c1b386..645a14a2 100644
--- a/src/routes/stack/deploy.rs
+++ b/src/routes/stack/deploy.rs
@@ -57,7 +57,7 @@ pub async fn add(
     return match stack {
         Some(stack) => {
             let id = stack.id.clone();
-            let mut dc = DcBuilder::new(stack);
+            let dc = DcBuilder::new(stack);
             dc.build();
 
             let addr = sets.amqp.connection_string();
diff --git a/src/startup.rs b/src/startup.rs
index 3306fcd2..3d38420a 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -27,7 +27,8 @@ pub async fn run(
     let server = HttpServer::new(move || {
         App::new()
             .wrap(TracingLogger::default())
-            .service(web::scope("/health_check").service(crate::routes::health_check))
+            .service(web::scope("/health_check")
+                .service(crate::routes::health_check))
             .service(
                 web::scope("/client")
                     .wrap(HttpAuthentication::bearer(
diff --git a/tests/custom-stack-payload-11.json b/tests/custom-stack-payload-11.json
new file mode 100644
index 00000000..7113b57a
--- /dev/null
+++ b/tests/custom-stack-payload-11.json
@@ -0,0 +1,2 @@
+
+{"commonDomain":"","domainList":{},"region":"fsn1","zone":null,"server":"cx21","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":["statuspanel","fail2ban","nginx_feature","nginx_proxy_manager"],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":true,"disk_type":"pd-standart","cloud_token":"D3dkDL4Qy1NpFezTl60V5RYfB5p0BSwLLoKVHhEqmJQ3jObSG77irP86e9YtCYVi","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"_etag":null,"_id":233,"_created":"2023-07-19T06:38:57.608807","_updated":"2023-11-17T11:38:12.904673","name":"FastAPI","code":"fastapi","role":["fastapi"],"type":"web","default":true,"popularity":null,"descr":null,"ports":{"public":["5050","8000","8080"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":2500,"height":2500,"image":"8d1ba06d-04e2-4523-879e-2846c86a10d8.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":null,"plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0.1,"ram_size":"0.2Gb","disk_size":"0.2Gb","dockerhub_image":"fastapi","form":null,"versions":[{"_etag":null,"_id":587,"_created":null,"_updated":"2023-07-20T12:51:20.321999","app_id":233,"name":"0.0.1","version":"0.0.1","update_status":"ready_for_testing","tag":"0.0.1"},{"_etag":null,"_id":590,"_created":null,"_updated":"2023-07-20T08:36:49.651219","app_id":233,"name":"0.0.1","version":"0.0.1","update_status":"ready_for_testing","tag":"latest"},{"_etag":null,"_id":589,"_created":null,"_updated":"2023-07-20T08:36:55.200575","app_id":233,"name":"0.0.1","version":"0.0.1","update_status":"ready_for_testing","tag":"unstable"},{"_etag":"591","_id":591,"_created":null,"_updated":"2023-08-15T08:17:40.226186","app_id":233,"name":"Fastapi","version":"0.100.0","update_status":"published","tag":"stable"}],"domain":"fastapi6.test","sharedPorts":["8080"],"main":true,"version":{"_etag":"591","_id":591,"_created":null,"_updated":"2023-08-15T08:17:40.226186","app_id":233,"name":"Fastapi","version":"0.100.0","update_status":"published","tag":"stable"}},{"name":"MyCustomApp6","code":"mycustomapp6","domain":"mycustomapp.fastapi6.test","sharedPorts":["3000"],"versions":[],"custom":true,"type":"web","main":false,"_id":0,"dockerhub_user":"vsilent","dockerhub_name":"smarty-bot","disk_size":"0.8Gb","ram_size":"1.6Gb","cpu":0.6}],"feature":[{"_etag":null,"_id":235,"_created":"2023-08-11T07:07:12.123355","_updated":"2023-09-07T14:19:30.709139","name":"Nginx Proxy Manager","code":"nginx_proxy_manager","role":["nginx_proxy_manager"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["80","81","443"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":192,"height":192,"image":"205128e6-0303-4b62-b946-9810b61f3d04.png"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\">Nginx Proxy Manager is a user-friendly software application designed to effortlessly route traffic to your websites, whether they&#x27;re hosted at home or elsewhere. It comes equipped with free SSL capabilities, eliminating the need for extensive Nginx or Letsencrypt knowledge. This tool proves especially handy for simplifying SSL generation and seamlessly proxying your docker containers.</p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"1","ram_size":"1Gb","disk_size":"0.3Gb","dockerhub_image":"nginx-proxy-manager","form":null,"category":[{"name":"integrated_features"}],"group":[],"versions":[{"_id":601,"name":"Nginx proxy manager","version":"2.10.4","update_status":"published","tag":"stable"}],"links":[],"version":{"_id":601,"name":"Nginx proxy manager","version":"2.10.4","update_status":"published","tag":"stable"}},{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\"><strong style=\"font-weight:bold\">Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)</strong></p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0.6,"ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","form":null,"versions":[{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-09-07T07:28:51.18965","app_id":198,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"}],"domain":"","sharedPorts":["9000"],"main":false,"version":{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-09-07T07:28:51.18965","app_id":198,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"}}],"service":[{"_etag":null,"_id":1,"_created":"2020-06-19T13:07:22.483252","_updated":"2023-08-08T10:38:12.307594","name":"MySQL","code":"mysql","role":[],"type":"service","default":null,"popularity":null,"descr":null,"ports":null,"commercial":null,"subscription":null,"autodeploy":false,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":256,"height":133,"image":"d83c4e8a-a3e9-4f06-8058-2392c846df31.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\">MySQL Server, the world&#x27;s most popular open-source database, and MySQL Cluster, a real-time, open-source transactional database.</p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0,"ram_size":null,"disk_size":null,"dockerhub_image":"mysql","form":null,"category":[null],"group":[{"_etag":null,"_id":12,"_created":null,"_updated":null,"name":"Database","code":"database"}],"versions":[{"_id":473,"name":"8.0.34","version":"8.0.34","update_status":"published","tag":"8.0.34"},{"_id":472,"name":"5.7.43","version":"5.7.43","update_status":"published","tag":"5.7.43"}],"links":[{"url":"https://dev.mysql.com","title":"MySQL","type":"vendor","follow":null},{"repo_owner":"mysql","repo_name":"mysql-server","type":"github","follow":null}],"domain":"","sharedPorts":[],"main":false,"version":{"_id":473,"name":"8.0.34","version":"8.0.34","update_status":"published","tag":"8.0.34"}}],"servers_count":3,"project_name":"FastAPI6","custom_stack_code":"fastapi6","project_git_url":"https://github.com/trydirect/fastapi.git"}}

From a5268ffc8be137cbf6e0fe5a6b6a4ab80a661de1 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Thu, 23 Nov 2023 22:26:15 +0200
Subject: [PATCH 002/284] project/stack existence check

---
 src/routes/stack/add.rs            | 29 +++++++++++++++++++++++++----
 tests/custom-stack-payload-11.json |  2 --
 2 files changed, 25 insertions(+), 6 deletions(-)
 delete mode 100644 tests/custom-stack-payload-11.json

diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs
index 4f08fe59..1d648153 100644
--- a/src/routes/stack/add.rs
+++ b/src/routes/stack/add.rs
@@ -11,10 +11,10 @@ use chrono::Utc;
 use serde_json::Value;
 use sqlx::PgPool;
 use std::str;
-use serde_valid::json::FromJsonValue;
 use serde_valid::Validate;
 use tracing::Instrument;
 use uuid::Uuid;
+use crate::models;
 
 
 #[tracing::instrument(name = "Add stack.")]
@@ -37,6 +37,30 @@ pub async fn add(
         }
     };
 
+    let stack_name = form.custom.custom_stack_code.clone();
+
+    let query_span = tracing::info_span!("Check project/stack existence by custom_stack_code.");
+    match sqlx::query_as!(
+        models::Stack,
+        r"SELECT * FROM user_stack WHERE name = $1",
+        stack_name
+    )
+        .fetch_one(pool.get_ref())
+        .instrument(query_span)
+        .await
+    {
+        Ok(record) => {
+            tracing::info!("record exists: id: {}, name: {}", record.id, record.name);
+            return JsonResponse::build().conflict("Stack with that name already exists"
+                .to_owned());
+        }
+        Err(sqlx::Error::RowNotFound) => {}
+        Err(e) => {
+            tracing::error!("Failed to fetch stack info, error: {:?}", e);
+            return JsonResponse::build().err(format!("Internal Server Error"));
+        }
+    };
+
     let user_id = user.id.clone();
     let request_id = Uuid::new_v4();
     let request_span = tracing::info_span!(
@@ -57,9 +81,6 @@ pub async fn add(
 
     let query_span = tracing::info_span!("Saving new stack details into the database");
 
-    let stack_name = form.custom.custom_stack_code.clone();
-
-
     let errors = form.validate().is_ok();
     println!("{:?}",errors);
     let body: Value = match serde_json::to_value::<StackForm>(form) {
diff --git a/tests/custom-stack-payload-11.json b/tests/custom-stack-payload-11.json
deleted file mode 100644
index 7113b57a..00000000
--- a/tests/custom-stack-payload-11.json
+++ /dev/null
@@ -1,2 +0,0 @@
-
-{"commonDomain":"","domainList":{},"region":"fsn1","zone":null,"server":"cx21","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":["statuspanel","fail2ban","nginx_feature","nginx_proxy_manager"],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":true,"disk_type":"pd-standart","cloud_token":"D3dkDL4Qy1NpFezTl60V5RYfB5p0BSwLLoKVHhEqmJQ3jObSG77irP86e9YtCYVi","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"_etag":null,"_id":233,"_created":"2023-07-19T06:38:57.608807","_updated":"2023-11-17T11:38:12.904673","name":"FastAPI","code":"fastapi","role":["fastapi"],"type":"web","default":true,"popularity":null,"descr":null,"ports":{"public":["5050","8000","8080"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":2500,"height":2500,"image":"8d1ba06d-04e2-4523-879e-2846c86a10d8.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":null,"plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0.1,"ram_size":"0.2Gb","disk_size":"0.2Gb","dockerhub_image":"fastapi","form":null,"versions":[{"_etag":null,"_id":587,"_created":null,"_updated":"2023-07-20T12:51:20.321999","app_id":233,"name":"0.0.1","version":"0.0.1","update_status":"ready_for_testing","tag":"0.0.1"},{"_etag":null,"_id":590,"_created":null,"_updated":"2023-07-20T08:36:49.651219","app_id":233,"name":"0.0.1","version":"0.0.1","update_status":"ready_for_testing","tag":"latest"},{"_etag":null,"_id":589,"_created":null,"_updated":"2023-07-20T08:36:55.200575","app_id":233,"name":"0.0.1","version":"0.0.1","update_status":"ready_for_testing","tag":"unstable"},{"_etag":"591","_id":591,"_created":null,"_updated":"2023-08-15T08:17:40.226186","app_id":233,"name":"Fastapi","version":"0.100.0","update_status":"published","tag":"stable"}],"domain":"fastapi6.test","sharedPorts":["8080"],"main":true,"version":{"_etag":"591","_id":591,"_created":null,"_updated":"2023-08-15T08:17:40.226186","app_id":233,"name":"Fastapi","version":"0.100.0","update_status":"published","tag":"stable"}},{"name":"MyCustomApp6","code":"mycustomapp6","domain":"mycustomapp.fastapi6.test","sharedPorts":["3000"],"versions":[],"custom":true,"type":"web","main":false,"_id":0,"dockerhub_user":"vsilent","dockerhub_name":"smarty-bot","disk_size":"0.8Gb","ram_size":"1.6Gb","cpu":0.6}],"feature":[{"_etag":null,"_id":235,"_created":"2023-08-11T07:07:12.123355","_updated":"2023-09-07T14:19:30.709139","name":"Nginx Proxy Manager","code":"nginx_proxy_manager","role":["nginx_proxy_manager"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["80","81","443"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":192,"height":192,"image":"205128e6-0303-4b62-b946-9810b61f3d04.png"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\">Nginx Proxy Manager is a user-friendly software application designed to effortlessly route traffic to your websites, whether they&#x27;re hosted at home or elsewhere. It comes equipped with free SSL capabilities, eliminating the need for extensive Nginx or Letsencrypt knowledge. This tool proves especially handy for simplifying SSL generation and seamlessly proxying your docker containers.</p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"1","ram_size":"1Gb","disk_size":"0.3Gb","dockerhub_image":"nginx-proxy-manager","form":null,"category":[{"name":"integrated_features"}],"group":[],"versions":[{"_id":601,"name":"Nginx proxy manager","version":"2.10.4","update_status":"published","tag":"stable"}],"links":[],"version":{"_id":601,"name":"Nginx proxy manager","version":"2.10.4","update_status":"published","tag":"stable"}},{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\"><strong style=\"font-weight:bold\">Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)</strong></p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0.6,"ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","form":null,"versions":[{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-09-07T07:28:51.18965","app_id":198,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"}],"domain":"","sharedPorts":["9000"],"main":false,"version":{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-09-07T07:28:51.18965","app_id":198,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"}}],"service":[{"_etag":null,"_id":1,"_created":"2020-06-19T13:07:22.483252","_updated":"2023-08-08T10:38:12.307594","name":"MySQL","code":"mysql","role":[],"type":"service","default":null,"popularity":null,"descr":null,"ports":null,"commercial":null,"subscription":null,"autodeploy":false,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":256,"height":133,"image":"d83c4e8a-a3e9-4f06-8058-2392c846df31.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\">MySQL Server, the world&#x27;s most popular open-source database, and MySQL Cluster, a real-time, open-source transactional database.</p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0,"ram_size":null,"disk_size":null,"dockerhub_image":"mysql","form":null,"category":[null],"group":[{"_etag":null,"_id":12,"_created":null,"_updated":null,"name":"Database","code":"database"}],"versions":[{"_id":473,"name":"8.0.34","version":"8.0.34","update_status":"published","tag":"8.0.34"},{"_id":472,"name":"5.7.43","version":"5.7.43","update_status":"published","tag":"5.7.43"}],"links":[{"url":"https://dev.mysql.com","title":"MySQL","type":"vendor","follow":null},{"repo_owner":"mysql","repo_name":"mysql-server","type":"github","follow":null}],"domain":"","sharedPorts":[],"main":false,"version":{"_id":473,"name":"8.0.34","version":"8.0.34","update_status":"published","tag":"8.0.34"}}],"servers_count":3,"project_name":"FastAPI6","custom_stack_code":"fastapi6","project_git_url":"https://github.com/trydirect/fastapi.git"}}

From c9780b96b5ea7da04809cb4f92cce66916816390 Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Fri, 24 Nov 2023 17:50:03 +0200
Subject: [PATCH 003/284] issue-auth debug for client secret

---
 src/models/client.rs | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/src/models/client.rs b/src/models/client.rs
index afef0c19..d8815970 100644
--- a/src/models/client.rs
+++ b/src/models/client.rs
@@ -9,10 +9,15 @@ pub struct Client {
 
 impl std::fmt::Debug for Client {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let secret: String = match self.secret.as_ref() {
+            Some(val) => val.chars().take(4).collect::<String>() + "****",
+            None => "".to_string(),
+        };
+
         write!(
             f,
-            "Client {{id: {:?}, user_id: {:?}}}",
-            self.id, self.user_id
+            "Client {{id: {:?}, user_id: {:?}, secret: {}}}",
+            self.id, self.user_id, secret
         )
     }
 }

From a4e10679d5dd4faa1aad5e92bb2e7a2be004dd1c Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Fri, 24 Nov 2023 18:47:58 +0200
Subject: [PATCH 004/284] issue-auth refactor

---
 src/helpers/json.rs          | 139 ++++++-----------------------------
 src/middleware/client.rs     |   1 +
 src/routes/client/add.rs     |   9 +--
 src/routes/client/disable.rs |   2 +-
 src/routes/client/enable.rs  |   2 +-
 src/routes/client/update.rs  |   2 +-
 src/routes/rating/add.rs     |  20 ++---
 src/routes/rating/get.rs     |  16 ++--
 src/routes/stack/add.rs      |  23 +++---
 src/routes/stack/compose.rs  |  23 +++---
 src/routes/stack/deploy.rs   |  50 ++++++-------
 src/routes/stack/get.rs      |  32 ++++----
 12 files changed, 99 insertions(+), 220 deletions(-)

diff --git a/src/helpers/json.rs b/src/helpers/json.rs
index 87e73273..efdfb0e7 100644
--- a/src/helpers/json.rs
+++ b/src/helpers/json.rs
@@ -1,15 +1,15 @@
-use actix_web::error::{ErrorBadRequest, ErrorConflict, ErrorNotFound, ErrorInternalServerError};
-use serde_derive::Serialize;
+use actix_web::error::{ErrorBadRequest, ErrorConflict, ErrorInternalServerError, ErrorNotFound};
 use actix_web::web::Json;
 use actix_web::Error;
 use actix_web::Result;
+use serde_derive::Serialize;
 
 #[derive(Serialize)]
 pub(crate) struct JsonResponse<T> {
     pub(crate) message: String,
     pub(crate) id: Option<i32>,
     pub(crate) item: Option<T>,
-    pub(crate) list: Option<Vec<T>>
+    pub(crate) list: Option<Vec<T>>,
 }
 
 #[derive(Serialize, Default)]
@@ -41,9 +41,9 @@ where
         self
     }
 
-    fn to_json_response(self, msg: String) -> JsonResponse<T> {
+    fn to_json_response<I: Into<String>>(self, msg: I) -> JsonResponse<T> {
         JsonResponse {
-            message: msg,
+            message: msg.into(),
             id: self.id,
             item: self.item,
             list: self.list,
@@ -51,63 +51,43 @@ where
     }
 
     pub(crate) fn ok<I: Into<String>>(self, msg: I) -> Result<Json<JsonResponse<T>>, Error> {
-        Ok(Json(self.to_json_response(msg.into())))
+        Ok(Json(self.to_json_response(msg)))
     }
 
-    pub(crate) fn err<I: Into<String>>(self, msg: I) -> Result<Json<JsonResponse<T>>, Error> {
-        let json_response = self.to_json_response(msg.into());
+    pub(crate) fn bad_request<I: Into<String>>(
+        self,
+        msg: I,
+    ) -> Result<Json<JsonResponse<T>>, Error> {
+        let json_response = self.to_json_response(msg);
 
         Err(ErrorBadRequest(
             serde_json::to_string(&json_response).unwrap(),
         ))
     }
 
-    pub(crate) fn not_found(self, msg: String) -> Result<Json<JsonResponse<T>>, Error>  {
-
-        let json_response = JsonResponse {
-            message: msg,
-            id: self.id,
-            item: self.item,
-            list: self.list
-        };
+    pub(crate) fn not_found<I: Into<String>>(self, msg: I) -> Result<Json<JsonResponse<T>>, Error> {
+        let json_response = self.to_json_response(msg);
 
         Err(ErrorNotFound(
-            serde_json::to_string(&json_response).unwrap()))
+            serde_json::to_string(&json_response).unwrap(),
+        ))
     }
 
-    pub(crate) fn internal_error(self, msg: String) -> Result<Json<JsonResponse<T>>, Error>  {
-
-        let json_response = JsonResponse {
-            message: msg,
-            id: self.id,
-            item: self.item,
-            list: self.list
-        };
+    pub(crate) fn internal_server_error<I: Into<String>>(
+        self,
+        msg: I,
+    ) -> Result<Json<JsonResponse<T>>, Error> {
+        let json_response = self.to_json_response(msg);
 
         Err(ErrorInternalServerError(
-            serde_json::to_string(&json_response).unwrap()))
+            serde_json::to_string(&json_response).unwrap(),
+        ))
     }
 
-    pub(crate) fn conflict(self, msg: String) -> Result<Json<JsonResponse<T>>, Error>  {
-
-        let json_response = JsonResponse {
-            message: msg,
-            id: self.id,
-            item: self.item,
-            list: self.list
-        };
+    pub(crate) fn conflict<I: Into<String>>(self, msg: I) -> Result<Json<JsonResponse<T>>, Error> {
+        let json_response = self.to_json_response(msg);
 
         Err(ErrorConflict(
-            serde_json::to_string(&json_response).unwrap()))
-    }
-
-    pub(crate) fn err_internal_server_error<I: Into<String>>(
-        self,
-        msg: I,
-    ) -> Result<Json<JsonResponse<T>>, Error> {
-        let json_response = self.to_json_response(msg.into());
-
-        Err(ErrorInternalServerError(
             serde_json::to_string(&json_response).unwrap(),
         ))
     }
@@ -120,75 +100,4 @@ where
     pub fn build() -> JsonResponseBuilder<T> {
         JsonResponseBuilder::default()
     }
-
-    pub(crate) fn new(message: String,
-                      id: Option<i32>,
-                      item:Option<T>,
-                      list: Option<Vec<T>>) -> Self {
-        tracing::debug!("Executed..");
-        JsonResponse {
-            message,
-            id,
-            item,
-            list,
-        }
-    }
-
-    // pub(crate) fn ok(id: i32, message: &str) -> JsonResponse<T> {
-    //
-    //     let msg = if !message.trim().is_empty() {
-    //         message.to_string()
-    //     }
-    //     else{
-    //         String::from("Success")
-    //     };
-    //
-    //     JsonResponse {
-    //         message: msg,
-    //         id: Some(id),
-    //         item: None,
-    //         list: None,
-    //     }
-    // }
-
-    // pub(crate) fn not_found() -> Self {
-    //     JsonResponse {
-    //         id: None,
-    //         item: None,
-    //         message: format!("Object not found"),
-    //         list: None,
-    //     }
-    // }
-    //
-    // pub(crate) fn internal_error(message: &str) -> Self {
-    //
-    //     let msg = if !message.trim().is_empty() {
-    //         message.to_string()
-    //     }
-    //     else{
-    //         String::from("Internal error")
-    //     };
-    //     JsonResponse {
-    //         id: None,
-    //         item: None,
-    //         message: msg,
-    //         list: None,
-    //     }
-    // }
-    //
-    // pub(crate) fn not_valid(message: &str) -> Self {
-    //
-    //     let msg = if !message.trim().is_empty() {
-    //         message.to_string()
-    //     }
-    //     else{
-    //         String::from("Validation error")
-    //     };
-    //     JsonResponse {
-    //         id: None,
-    //         item: None,
-    //         message: msg,
-    //         list: None,
-    //     }
-    // }
 }
diff --git a/src/middleware/client.rs b/src/middleware/client.rs
index dc76c446..65f40028 100644
--- a/src/middleware/client.rs
+++ b/src/middleware/client.rs
@@ -1,3 +1,4 @@
+use crate::helpers::JsonResponse;
 use crate::models::Client;
 use actix_http::header::CONTENT_LENGTH;
 use actix_web::error::{ErrorForbidden, ErrorInternalServerError, ErrorNotFound, PayloadError};
diff --git a/src/routes/client/add.rs b/src/routes/client/add.rs
index eee2ce07..1b9b709e 100644
--- a/src/routes/client/add.rs
+++ b/src/routes/client/add.rs
@@ -9,7 +9,6 @@ use sqlx::PgPool;
 use std::sync::Arc;
 use tracing::Instrument;
 
-
 #[tracing::instrument(name = "Add client.")]
 #[post("")]
 pub async fn add_handler(
@@ -41,12 +40,12 @@ pub async fn add_handler(
                     client_count
                 );
 
-                return JsonResponse::build().err("Too many clients created".to_owned());
+                return JsonResponse::build().bad_request("Too many clients created");
             }
         }
         Err(e) => {
             tracing::error!("Failed to execute query: {:?}", e);
-            return JsonResponse::build().internal_error("Internal Server Error".to_owned());
+            return JsonResponse::build().internal_server_error("Internal Server Error");
         }
     };
 
@@ -79,12 +78,12 @@ pub async fn add_handler(
             return JsonResponse::build()
                 .set_id(client.id)
                 .set_item(Some(client))
-                .ok("OK".to_owned());
+                .ok("OK");
         }
         Err(e) => {
             tracing::error!("Failed to execute query: {:?}", e);
             let err = format!("Failed to insert. {}", e);
-            return JsonResponse::build().err(err);
+            return JsonResponse::build().bad_request(err);
         }
     }
 }
diff --git a/src/routes/client/disable.rs b/src/routes/client/disable.rs
index 383130b9..3ec7154c 100644
--- a/src/routes/client/disable.rs
+++ b/src/routes/client/disable.rs
@@ -62,7 +62,7 @@ pub async fn disable_handler(
         }
         Err(e) => {
             tracing::error!("Failed to execute query: {:?}", e);
-            JsonResponse::build().err("")
+            JsonResponse::build().bad_request("")
         }
     }
 }
diff --git a/src/routes/client/enable.rs b/src/routes/client/enable.rs
index a870c655..f10bf094 100644
--- a/src/routes/client/enable.rs
+++ b/src/routes/client/enable.rs
@@ -69,7 +69,7 @@ pub async fn enable_handler(
         }
         Err(e) => {
             tracing::error!("Failed to execute query: {:?}", e);
-            JsonResponse::build().err_internal_server_error("")
+            JsonResponse::build().internal_server_error("")
         }
     }
 }
diff --git a/src/routes/client/update.rs b/src/routes/client/update.rs
index f50b4ed7..c833508d 100644
--- a/src/routes/client/update.rs
+++ b/src/routes/client/update.rs
@@ -68,7 +68,7 @@ pub async fn update_handler(
         }
         Err(e) => {
             tracing::error!("Failed to execute query: {:?}", e);
-            JsonResponse::build().err_internal_server_error("")
+            JsonResponse::build().internal_server_error("")
         }
     }
 }
diff --git a/src/routes/rating/add.rs b/src/routes/rating/add.rs
index dd13eeeb..b099cfe7 100644
--- a/src/routes/rating/add.rs
+++ b/src/routes/rating/add.rs
@@ -3,20 +3,15 @@ use crate::helpers::JsonResponse;
 use crate::models;
 use crate::models::user::User;
 use crate::models::RateCategory;
+use actix_web::{post, web, Responder, Result};
 use sqlx::PgPool;
 use tracing::Instrument;
-use actix_web::{
-    web,
-    post,
-    Responder, Result,
-};
 
 // workflow
 // add, update, list, get(user_id), ACL,
 // ACL - access to func for a user
 // ACL - access to objects for a user
 
-
 #[tracing::instrument(name = "Add rating.")]
 #[post("")]
 pub async fn add_handler(
@@ -39,8 +34,7 @@ pub async fn add_handler(
         }
         Err(e) => {
             tracing::error!("Failed to fetch product: {:?}, error: {:?}", form.obj_id, e);
-            return JsonResponse::<models::Rating>::build()
-                .err(format!("Object not found {}", form.obj_id));
+            return JsonResponse::<models::Rating>::build().bad_request("Object not found");
         }
     };
 
@@ -63,12 +57,12 @@ pub async fn add_handler(
                 form.obj_id,
                 form.category
             );
-            return JsonResponse::build().conflict("Already rated".to_owned());
+            return JsonResponse::build().conflict("Already rated");
         }
         Err(sqlx::Error::RowNotFound) => {}
         Err(e) => {
             tracing::error!("Failed to fetch rating, error: {:?}", e);
-            return JsonResponse::build().err(format!("Internal Server Error"));
+            return JsonResponse::build().internal_server_error("Internal Server Error");
         }
     }
 
@@ -96,13 +90,11 @@ pub async fn add_handler(
         Ok(result) => {
             tracing::info!("New rating {} have been saved to database", result.id);
 
-            JsonResponse::build()
-                .set_id(result.id)
-                .ok("Saved".to_owned())
+            JsonResponse::build().set_id(result.id).ok("Saved")
         }
         Err(e) => {
             tracing::error!("Failed to execute query: {:?}", e);
-            JsonResponse::build().internal_error("Failed to insert".to_owned())
+            JsonResponse::build().internal_server_error("Failed to insert")
         }
     }
 }
diff --git a/src/routes/rating/get.rs b/src/routes/rating/get.rs
index 75281c0e..e781d9a8 100644
--- a/src/routes/rating/get.rs
+++ b/src/routes/rating/get.rs
@@ -32,23 +32,19 @@ pub async fn get_handler(
             return JsonResponse::build().set_item(Some(rating)).ok("OK");
         }
         Err(sqlx::Error::RowNotFound) => {
-            return JsonResponse::build().err("Not Found");
+            return JsonResponse::build().not_found("");
         }
         Err(e) => {
             tracing::error!("Failed to fetch rating, error: {:?}", e);
-            return JsonResponse::build().err("Internal Server Error");
+            return JsonResponse::build().internal_server_error("");
         }
     }
 }
 
 #[tracing::instrument(name = "Get all ratings.")]
 #[get("")]
-pub async fn list_handler(
-    path: web::Path<()>,
-    pool: web::Data<PgPool>,
-) -> Result<impl Responder> {
+pub async fn list_handler(path: web::Path<()>, pool: web::Data<PgPool>) -> Result<impl Responder> {
     /// Get ratings of all users
-
     let query_span = tracing::info_span!("Get all rates.");
     // let category = path.0;
     match sqlx::query_as!(models::Rating, r"SELECT * FROM rating")
@@ -58,14 +54,14 @@ pub async fn list_handler(
     {
         Ok(rating) => {
             tracing::info!("Ratings found: {:?}", rating.len());
-            return JsonResponse::build().set_list(rating).ok("OK".to_owned());
+            return JsonResponse::build().set_list(rating).ok("OK");
         }
         Err(sqlx::Error::RowNotFound) => {
-            return JsonResponse::build().not_found("Not Found".to_owned());
+            return JsonResponse::build().not_found("");
         }
         Err(e) => {
             tracing::error!("Failed to fetch rating, error: {:?}", e);
-            return JsonResponse::build().internal_error("Internal Server Error".to_owned());
+            return JsonResponse::build().internal_server_error("");
         }
     }
 }
diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs
index 27260e04..8176cfa5 100644
--- a/src/routes/stack/add.rs
+++ b/src/routes/stack/add.rs
@@ -1,20 +1,19 @@
+use crate::forms::stack::StackForm;
+use crate::helpers::JsonResponse;
+use crate::models;
+use crate::models::user::User;
+use actix_web::post;
 use actix_web::{
     web,
     web::{Bytes, Data},
     Responder, Result,
 };
-use crate::forms::stack::StackForm;
-use crate::helpers::JsonResponse;
-use crate::models::user::User;
-use actix_web::post;
 use chrono::Utc;
 use serde_json::Value;
 use sqlx::PgPool;
 use std::str;
 use tracing::Instrument;
 use uuid::Uuid;
-use crate::models;
-
 
 #[tracing::instrument(name = "Add stack.")]
 #[post("")]
@@ -23,16 +22,13 @@ pub async fn add(
     user: web::ReqData<User>,
     pool: Data<PgPool>,
 ) -> Result<impl Responder> {
-
     let body_bytes = actix_web::body::to_bytes(body).await.unwrap();
     let body_str = str::from_utf8(&body_bytes).unwrap();
     let form = match serde_json::from_str::<StackForm>(body_str) {
-        Ok(f) => {
-            f
-        }
+        Ok(f) => f,
         Err(_err) => {
             let msg = format!("Invalid data. {:?}", _err);
-            return JsonResponse::<StackForm>::build().err("Invalid data".to_owned());
+            return JsonResponse::<StackForm>::build().bad_request("Invalid data");
         }
     };
 
@@ -87,12 +83,11 @@ pub async fn add(
                 "req_id: {} New stack details have been saved to database",
                 request_id
             );
-            return JsonResponse::build().set_id(record.id).ok("OK".to_owned());
+            return JsonResponse::build().set_id(record.id).ok("OK");
         }
         Err(e) => {
             tracing::error!("req_id: {} Failed to execute query: {:?}", request_id, e);
-            return JsonResponse::build().err("Internal Server Error".to_owned());
+            return JsonResponse::build().bad_request("Internal Server Error");
         }
     };
 }
-
diff --git a/src/routes/stack/compose.rs b/src/routes/stack/compose.rs
index 4d19a3f5..7b2f8a4c 100644
--- a/src/routes/stack/compose.rs
+++ b/src/routes/stack/compose.rs
@@ -4,6 +4,7 @@ use actix_web::{
     Responder, Result,
 };
 
+use crate::helpers::stack::builder::DcBuilder;
 use crate::helpers::JsonResponse;
 use crate::models::user::User;
 use crate::models::Stack;
@@ -12,7 +13,6 @@ use sqlx::PgPool;
 use std::str;
 use tracing::Instrument;
 use uuid::Uuid;
-use crate::helpers::stack::builder::DcBuilder;
 
 #[tracing::instrument(name = "User's generate docker-compose.")]
 #[post("/{id}")]
@@ -29,10 +29,11 @@ pub async fn add(
         r#"
         SELECT * FROM user_stack WHERE id=$1 AND user_id=$2 LIMIT 1
         "#,
-        id, user.id
+        id,
+        user.id
     )
-        .fetch_one(pool.get_ref())
-        .await
+    .fetch_one(pool.get_ref())
+    .await
     {
         Ok(stack) => {
             tracing::info!("stack found: {:?}", stack.id,);
@@ -58,10 +59,10 @@ pub async fn add(
             return JsonResponse::build()
                 .set_id(id)
                 .set_item(fc.unwrap())
-                .ok("Success".to_owned());
+                .ok("Success");
         }
         None => {
-            return JsonResponse::build().err("Could not generate compose file".to_owned());
+            return JsonResponse::build().bad_request("Could not generate compose file");
         }
     }
 }
@@ -84,8 +85,8 @@ pub async fn admin(
         "#,
         id,
     )
-        .fetch_one(pool.get_ref())
-        .await
+    .fetch_one(pool.get_ref())
+    .await
     {
         Ok(stack) => {
             tracing::info!("stack found: {:?}", stack.id,);
@@ -109,11 +110,11 @@ pub async fn admin(
             // tracing::debug!("Docker compose file content {:?}", fc);
             return JsonResponse::build()
                 .set_id(id)
-                .set_item(fc.unwrap()).ok("Success".to_owned());
-
+                .set_item(fc.unwrap())
+                .ok("Success");
         }
         None => {
-            return JsonResponse::build().err("Could not generate compose file".to_owned());
+            return JsonResponse::build().bad_request("Could not generate compose file");
         }
     }
 }
diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs
index 45c1b386..b76b63cf 100644
--- a/src/routes/stack/deploy.rs
+++ b/src/routes/stack/deploy.rs
@@ -1,23 +1,16 @@
-use std::sync::Arc;
-use actix_web::{
-    web,
-    post,
-    web::Data,
-    Responder, Result,
-};
-use crate::models::user::User;
-use crate::models::stack::Stack;
-use sqlx::PgPool;
-use lapin::{
-    options::*, publisher_confirm::Confirmation, BasicProperties, Connection,
-    ConnectionProperties
-};
 use crate::configuration::Settings;
-use crate::helpers::JsonResponse;
+use crate::forms::StackPayload;
 use crate::helpers::stack::builder::DcBuilder;
+use crate::helpers::JsonResponse;
+use crate::models::stack::Stack;
+use crate::models::user::User;
+use actix_web::{post, web, web::Data, Responder, Result};
 use futures_lite::stream::StreamExt;
-use crate::forms::StackPayload;
-
+use lapin::{
+    options::*, publisher_confirm::Confirmation, BasicProperties, Connection, ConnectionProperties,
+};
+use sqlx::PgPool;
+use std::sync::Arc;
 
 #[tracing::instrument(name = "Deploy for every user. Admin endpoint")]
 #[post("/{id}/deploy")]
@@ -37,8 +30,8 @@ pub async fn add(
         "#,
         id
     )
-        .fetch_one(pool.get_ref())
-        .await
+    .fetch_one(pool.get_ref())
+    .await
     {
         Ok(stack) => {
             tracing::info!("Stack found: {:?}", stack.id,);
@@ -71,9 +64,8 @@ pub async fn add(
             tracing::info!("RABBITMQ CONNECTED");
 
             let channel = conn.create_channel().await.unwrap();
-            let mut stack_data = serde_json::from_value::<StackPayload>(
-                dc.stack.body.clone()
-            ).unwrap();
+            let mut stack_data =
+                serde_json::from_value::<StackPayload>(dc.stack.body.clone()).unwrap();
 
             stack_data.id = Some(id);
             stack_data.user_token = Some(user.id.clone());
@@ -91,15 +83,15 @@ pub async fn add(
                     _payload,
                     BasicProperties::default(),
                 )
-                .await.unwrap()
-                .await.unwrap();
+                .await
+                .unwrap()
+                .await
+                .unwrap();
 
             assert_eq!(confirm, Confirmation::NotRequested);
             tracing::debug!("Message sent to rabbitmq");
-            return JsonResponse::<Stack>::build().set_id(id).ok("Success".to_owned());
+            return JsonResponse::<Stack>::build().set_id(id).ok("Success");
         }
-        None => {
-            JsonResponse::build().internal_error("Deployment failed".to_owned())
-        }
-    }
+        None => JsonResponse::build().internal_server_error("Deployment failed"),
+    };
 }
diff --git a/src/routes/stack/get.rs b/src/routes/stack/get.rs
index 271d2dcb..2264a144 100644
--- a/src/routes/stack/get.rs
+++ b/src/routes/stack/get.rs
@@ -1,12 +1,11 @@
-use actix_web::{web, get, Responder, Result};
-use sqlx::PgPool;
 use crate::helpers::{JsonResponse, JsonResponseBuilder};
 use crate::models;
 use crate::models::user::User;
+use actix_web::{get, web, Responder, Result};
+use sqlx::PgPool;
 use std::convert::From;
 use tracing::Instrument;
 
-
 #[tracing::instrument(name = "Get logged user stack.")]
 #[get("/{id}")]
 pub async fn item(
@@ -15,7 +14,6 @@ pub async fn item(
     pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
     /// Get stack apps of logged user only
-
     let (id,) = path.into_inner();
 
     tracing::info!("User {:?} gets stack by id {:?}", user.id, id);
@@ -24,21 +22,20 @@ pub async fn item(
         r#"
         SELECT * FROM user_stack WHERE id=$1 AND user_id=$2 LIMIT 1
         "#,
-        id, user.id
+        id,
+        user.id
     )
     .fetch_one(pool.get_ref())
     .await
     {
         Ok(stack) => {
             tracing::info!("Stack found: {:?}", stack.id,);
-            return JsonResponse::build().set_item(Some(stack)).ok("OK".to_owned());
-        }
-        Err(sqlx::Error::RowNotFound) => {
-            JsonResponse::build().not_found("Record not found".to_owned())
+            return JsonResponse::build().set_item(Some(stack)).ok("OK");
         }
+        Err(sqlx::Error::RowNotFound) => JsonResponse::build().not_found("Record not found"),
         Err(e) => {
             tracing::error!("Failed to fetch stack, error: {:?}", e);
-            return JsonResponse::build().internal_error("Could not fetch data".to_owned());
+            return JsonResponse::build().internal_server_error("Could not fetch data");
         }
     }
 }
@@ -50,11 +47,9 @@ pub async fn list(
     path: web::Path<(String,)>,
     pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
-
     /// This is admin endpoint, used by a m2m app, client app is confidential
     /// it should return stacks by user id
     /// in order to pass validation at external deployment service
-
     let (id,) = path.into_inner();
     tracing::info!("Logged user: {:?}", user.id);
     tracing::info!("Get stack list for user {:?}", id);
@@ -68,21 +63,20 @@ pub async fn list(
         "#,
         id
     )
-        .fetch_all(pool.get_ref())
-        .instrument(query_span)
-        .await
+    .fetch_all(pool.get_ref())
+    .instrument(query_span)
+    .await
     {
         Ok(list) => {
-            return JsonResponse::build().set_list(list).ok("OK".to_string());
+            return JsonResponse::build().set_list(list).ok("OK");
         }
         Err(sqlx::Error::RowNotFound) => {
             tracing::error!("No stacks found for user: {:?}", &user.id);
-            return JsonResponse::build().not_found("No stacks found for user".to_string())
+            return JsonResponse::build().not_found("No stacks found for user");
         }
         Err(e) => {
             tracing::error!("Failed to fetch stack, error: {:?}", e);
-            return JsonResponse::build().internal_error("Could not fetch".to_string());
+            return JsonResponse::build().internal_server_error("Could not fetch");
         }
     }
 }
-

From a49e5c7f84548b7391a67e7c26a341afcd04c235 Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 25 Nov 2023 05:09:23 +0200
Subject: [PATCH 005/284] issue-auth skip_serializing_if

---
 src/helpers/json.rs | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/helpers/json.rs b/src/helpers/json.rs
index efdfb0e7..cd86a44e 100644
--- a/src/helpers/json.rs
+++ b/src/helpers/json.rs
@@ -7,8 +7,11 @@ use serde_derive::Serialize;
 #[derive(Serialize)]
 pub(crate) struct JsonResponse<T> {
     pub(crate) message: String,
+    #[serde(skip_serializing_if = "Option::is_none")]
     pub(crate) id: Option<i32>,
+    #[serde(skip_serializing_if = "Option::is_none")]
     pub(crate) item: Option<T>,
+    #[serde(skip_serializing_if = "Option::is_none")]
     pub(crate) list: Option<Vec<T>>,
 }
 

From 8c510f792c04e5ed528902632dddc8443875a4a4 Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 25 Nov 2023 05:56:31 +0200
Subject: [PATCH 006/284] issue-auth to_string

---
 src/helpers/json.rs | 29 +++++++++--------------------
 1 file changed, 9 insertions(+), 20 deletions(-)

diff --git a/src/helpers/json.rs b/src/helpers/json.rs
index cd86a44e..f7ec87f4 100644
--- a/src/helpers/json.rs
+++ b/src/helpers/json.rs
@@ -53,6 +53,11 @@ where
         }
     }
 
+    fn to_string<I: Into<String>>(self, msg: I) -> String {
+        let json_response = self.to_json_response(msg);
+        serde_json::to_string(&json_response).unwrap()
+    }
+
     pub(crate) fn ok<I: Into<String>>(self, msg: I) -> Result<Json<JsonResponse<T>>, Error> {
         Ok(Json(self.to_json_response(msg)))
     }
@@ -61,38 +66,22 @@ where
         self,
         msg: I,
     ) -> Result<Json<JsonResponse<T>>, Error> {
-        let json_response = self.to_json_response(msg);
-
-        Err(ErrorBadRequest(
-            serde_json::to_string(&json_response).unwrap(),
-        ))
+        Err(ErrorBadRequest(self.to_string(msg)))
     }
 
     pub(crate) fn not_found<I: Into<String>>(self, msg: I) -> Result<Json<JsonResponse<T>>, Error> {
-        let json_response = self.to_json_response(msg);
-
-        Err(ErrorNotFound(
-            serde_json::to_string(&json_response).unwrap(),
-        ))
+        Err(ErrorNotFound(self.to_string(msg)))
     }
 
     pub(crate) fn internal_server_error<I: Into<String>>(
         self,
         msg: I,
     ) -> Result<Json<JsonResponse<T>>, Error> {
-        let json_response = self.to_json_response(msg);
-
-        Err(ErrorInternalServerError(
-            serde_json::to_string(&json_response).unwrap(),
-        ))
+        Err(ErrorInternalServerError(self.to_string(msg)))
     }
 
     pub(crate) fn conflict<I: Into<String>>(self, msg: I) -> Result<Json<JsonResponse<T>>, Error> {
-        let json_response = self.to_json_response(msg);
-
-        Err(ErrorConflict(
-            serde_json::to_string(&json_response).unwrap(),
-        ))
+        Err(ErrorConflict(self.to_string(msg)))
     }
 }
 

From 02fe07d6ccc0d16aa53eb8ac6c70d4c9714a2dba Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 25 Nov 2023 05:59:05 +0200
Subject: [PATCH 007/284] issue-auth removed set_id in client add

---
 src/routes/client/add.rs | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/src/routes/client/add.rs b/src/routes/client/add.rs
index 1b9b709e..21aba10d 100644
--- a/src/routes/client/add.rs
+++ b/src/routes/client/add.rs
@@ -75,10 +75,7 @@ pub async fn add_handler(
             tracing::info!("New client {} have been saved to database", result.id);
             client.id = result.id;
 
-            return JsonResponse::build()
-                .set_id(client.id)
-                .set_item(Some(client))
-                .ok("OK");
+            return JsonResponse::build().set_item(Some(client)).ok("OK");
         }
         Err(e) => {
             tracing::error!("Failed to execute query: {:?}", e);

From a2c7bf6216f08761fb2281d97de0a2836e8d9d1a Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 25 Nov 2023 06:07:17 +0200
Subject: [PATCH 008/284] issue-auth client middleware + JsonResponse

---
 src/helpers/json.rs      |  2 +-
 src/middleware/client.rs | 30 ++++++++++++++++++++++--------
 2 files changed, 23 insertions(+), 9 deletions(-)

diff --git a/src/helpers/json.rs b/src/helpers/json.rs
index f7ec87f4..419ad5d0 100644
--- a/src/helpers/json.rs
+++ b/src/helpers/json.rs
@@ -53,7 +53,7 @@ where
         }
     }
 
-    fn to_string<I: Into<String>>(self, msg: I) -> String {
+    pub(crate) fn to_string<I: Into<String>>(self, msg: I) -> String {
         let json_response = self.to_json_response(msg);
         serde_json::to_string(&json_response).unwrap()
     }
diff --git a/src/middleware/client.rs b/src/middleware/client.rs
index 65f40028..99df23f0 100644
--- a/src/middleware/client.rs
+++ b/src/middleware/client.rs
@@ -74,8 +74,10 @@ where
     fn call(&self, mut req: ServiceRequest) -> Self::Future {
         let service = self.service.clone();
         async move {
-            let client_id: i32 = get_header(&req, "stacker-id").map_err(|m| ErrorBadRequest(m))?;
-            let hash: String = get_header(&req, "stacker-hash").map_err(|m| ErrorBadRequest(m))?;
+            let client_id: i32 = get_header(&req, "stacker-id")
+                .map_err(|m| ErrorBadRequest(JsonResponse::<Client>::build().to_string(m)))?;
+            let hash: String = get_header(&req, "stacker-hash")
+                .map_err(|m| ErrorBadRequest(JsonResponse::<Client>::build().to_string(m)))?;
 
             let query_span = tracing::info_span!("Fetching the client by ID");
             let db_pool = req.app_data::<web::Data<Pool<Postgres>>>().unwrap();
@@ -96,15 +98,21 @@ where
             {
                 Ok(client) if client.secret.is_some() => client,
                 Ok(_client) => {
-                    return Err(ErrorForbidden("client is not active"));
+                    return Err(ErrorForbidden(
+                        JsonResponse::<Client>::build().to_string("client is not active"),
+                    ));
                 }
                 Err(sqlx::Error::RowNotFound) => {
-                    return Err(ErrorNotFound("the client is not found"));
+                    return Err(ErrorNotFound(
+                        JsonResponse::<Client>::build().to_string("the client is not found"),
+                    ));
                 }
                 Err(e) => {
                     tracing::error!("Failed to execute fetch query: {:?}", e);
 
-                    return Err(ErrorInternalServerError(""));
+                    return Err(ErrorInternalServerError(
+                        JsonResponse::<Client>::build().to_string(""),
+                    ));
                 }
             };
 
@@ -129,14 +137,18 @@ where
                     Err(err) => {
                         tracing::error!("error generating hmac {err:?}");
 
-                        return Err(ErrorInternalServerError(""));
+                        return Err(ErrorInternalServerError(
+                            JsonResponse::<Client>::build().to_string(""),
+                        ));
                     }
                 };
 
             mac.update(body.as_ref());
             let computed_hash = format!("{:x}", mac.finalize().into_bytes());
             if hash != computed_hash {
-                return Err(ErrorBadRequest("hash is wrong"));
+                return Err(ErrorBadRequest(
+                    JsonResponse::<Client>::build().to_string("hash is wrong"),
+                ));
             }
 
             let (_, mut payload) = actix_http::h1::Payload::create(true);
@@ -146,7 +158,9 @@ where
             match req.extensions_mut().insert(Arc::new(client)) {
                 Some(_) => {
                     tracing::error!("client middleware already called once");
-                    return Err(ErrorInternalServerError(""));
+                    return Err(ErrorInternalServerError(
+                        JsonResponse::<Client>::build().to_string(""),
+                    ));
                 }
                 None => {}
             }

From 53e96690c3531d471f902eeec1a187dd44844a41 Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 25 Nov 2023 06:27:29 +0200
Subject: [PATCH 009/284] issue-auth set_msg

---
 src/helpers/json.rs          | 24 +++++++++++++++---------
 src/middleware/client.rs     | 28 ++++++++++++++++++----------
 src/routes/client/add.rs     |  4 +++-
 src/routes/client/disable.rs |  4 +++-
 src/routes/client/enable.rs  |  2 +-
 src/routes/client/update.rs  |  4 ++--
 6 files changed, 42 insertions(+), 24 deletions(-)

diff --git a/src/helpers/json.rs b/src/helpers/json.rs
index 419ad5d0..8990788d 100644
--- a/src/helpers/json.rs
+++ b/src/helpers/json.rs
@@ -20,6 +20,7 @@ pub struct JsonResponseBuilder<T>
 where
     T: serde::Serialize + Default,
 {
+    message: String,
     id: Option<i32>,
     item: Option<T>,
     list: Option<Vec<T>>,
@@ -29,6 +30,11 @@ impl<T> JsonResponseBuilder<T>
 where
     T: serde::Serialize + Default,
 {
+    pub(crate) fn set_msg<I: Into<String>>(mut self, msg: I) -> Self {
+        self.message = msg.into();
+        self
+    }
+
     pub(crate) fn set_item(mut self, item: T) -> Self {
         self.item = Some(item);
         self
@@ -44,44 +50,44 @@ where
         self
     }
 
-    fn to_json_response<I: Into<String>>(self, msg: I) -> JsonResponse<T> {
+    fn to_json_response(self) -> JsonResponse<T> {
         JsonResponse {
-            message: msg.into(),
+            message: self.message,
             id: self.id,
             item: self.item,
             list: self.list,
         }
     }
 
-    pub(crate) fn to_string<I: Into<String>>(self, msg: I) -> String {
-        let json_response = self.to_json_response(msg);
+    pub(crate) fn to_string(self) -> String {
+        let json_response = self.to_json_response();
         serde_json::to_string(&json_response).unwrap()
     }
 
     pub(crate) fn ok<I: Into<String>>(self, msg: I) -> Result<Json<JsonResponse<T>>, Error> {
-        Ok(Json(self.to_json_response(msg)))
+        Ok(Json(self.set_msg(msg).to_json_response()))
     }
 
     pub(crate) fn bad_request<I: Into<String>>(
         self,
         msg: I,
     ) -> Result<Json<JsonResponse<T>>, Error> {
-        Err(ErrorBadRequest(self.to_string(msg)))
+        Err(ErrorBadRequest(self.set_msg(msg).to_string()))
     }
 
     pub(crate) fn not_found<I: Into<String>>(self, msg: I) -> Result<Json<JsonResponse<T>>, Error> {
-        Err(ErrorNotFound(self.to_string(msg)))
+        Err(ErrorNotFound(self.set_msg(msg).to_string()))
     }
 
     pub(crate) fn internal_server_error<I: Into<String>>(
         self,
         msg: I,
     ) -> Result<Json<JsonResponse<T>>, Error> {
-        Err(ErrorInternalServerError(self.to_string(msg)))
+        Err(ErrorInternalServerError(self.set_msg(msg).to_string()))
     }
 
     pub(crate) fn conflict<I: Into<String>>(self, msg: I) -> Result<Json<JsonResponse<T>>, Error> {
-        Err(ErrorConflict(self.to_string(msg)))
+        Err(ErrorConflict(self.set_msg(msg).to_string()))
     }
 }
 
diff --git a/src/middleware/client.rs b/src/middleware/client.rs
index 99df23f0..29931d6d 100644
--- a/src/middleware/client.rs
+++ b/src/middleware/client.rs
@@ -74,10 +74,12 @@ where
     fn call(&self, mut req: ServiceRequest) -> Self::Future {
         let service = self.service.clone();
         async move {
-            let client_id: i32 = get_header(&req, "stacker-id")
-                .map_err(|m| ErrorBadRequest(JsonResponse::<Client>::build().to_string(m)))?;
-            let hash: String = get_header(&req, "stacker-hash")
-                .map_err(|m| ErrorBadRequest(JsonResponse::<Client>::build().to_string(m)))?;
+            let client_id: i32 = get_header(&req, "stacker-id").map_err(|m| {
+                ErrorBadRequest(JsonResponse::<Client>::build().set_msg(m).to_string())
+            })?;
+            let hash: String = get_header(&req, "stacker-hash").map_err(|m| {
+                ErrorBadRequest(JsonResponse::<Client>::build().set_msg(m).to_string())
+            })?;
 
             let query_span = tracing::info_span!("Fetching the client by ID");
             let db_pool = req.app_data::<web::Data<Pool<Postgres>>>().unwrap();
@@ -99,19 +101,23 @@ where
                 Ok(client) if client.secret.is_some() => client,
                 Ok(_client) => {
                     return Err(ErrorForbidden(
-                        JsonResponse::<Client>::build().to_string("client is not active"),
+                        JsonResponse::<Client>::build()
+                            .set_msg("client is not active")
+                            .to_string(),
                     ));
                 }
                 Err(sqlx::Error::RowNotFound) => {
                     return Err(ErrorNotFound(
-                        JsonResponse::<Client>::build().to_string("the client is not found"),
+                        JsonResponse::<Client>::build()
+                            .set_msg("the client is not found")
+                            .to_string(),
                     ));
                 }
                 Err(e) => {
                     tracing::error!("Failed to execute fetch query: {:?}", e);
 
                     return Err(ErrorInternalServerError(
-                        JsonResponse::<Client>::build().to_string(""),
+                        JsonResponse::<Client>::build().to_string(),
                     ));
                 }
             };
@@ -138,7 +144,7 @@ where
                         tracing::error!("error generating hmac {err:?}");
 
                         return Err(ErrorInternalServerError(
-                            JsonResponse::<Client>::build().to_string(""),
+                            JsonResponse::<Client>::build().to_string(),
                         ));
                     }
                 };
@@ -147,7 +153,9 @@ where
             let computed_hash = format!("{:x}", mac.finalize().into_bytes());
             if hash != computed_hash {
                 return Err(ErrorBadRequest(
-                    JsonResponse::<Client>::build().to_string("hash is wrong"),
+                    JsonResponse::<Client>::build()
+                        .set_msg("hash is wrong")
+                        .to_string(),
                 ));
             }
 
@@ -159,7 +167,7 @@ where
                 Some(_) => {
                     tracing::error!("client middleware already called once");
                     return Err(ErrorInternalServerError(
-                        JsonResponse::<Client>::build().to_string(""),
+                        JsonResponse::<Client>::build().to_string(),
                     ));
                 }
                 None => {}
diff --git a/src/routes/client/add.rs b/src/routes/client/add.rs
index 21aba10d..9e722e35 100644
--- a/src/routes/client/add.rs
+++ b/src/routes/client/add.rs
@@ -55,7 +55,9 @@ pub async fn add_handler(
     client.secret = client::generate_secret(pool.get_ref(), 255)
         .await
         .map(|s| Some(s))
-        .map_err(|s| ErrorInternalServerError(s))?; //todo move to helpers::JsonResponse
+        .map_err(|s| {
+            ErrorInternalServerError(JsonResponse::<Client>::build().set_msg(s).to_string())
+        })?;
 
     let query_span = tracing::info_span!("Saving new client into the database");
     match sqlx::query!(
diff --git a/src/routes/client/disable.rs b/src/routes/client/disable.rs
index 3ec7154c..f9a4a50e 100644
--- a/src/routes/client/disable.rs
+++ b/src/routes/client/disable.rs
@@ -39,7 +39,9 @@ pub async fn disable_handler(
             Err("")
         }
     }
-    .map_err(|s| ErrorInternalServerError(s))?; //todo
+    .map_err(|s| {
+        ErrorInternalServerError(JsonResponse::<Client>::build().set_msg(s).to_string())
+    })?;
 
     client.secret = None;
     let query_span = tracing::info_span!("Updating client into the database");
diff --git a/src/routes/client/enable.rs b/src/routes/client/enable.rs
index f10bf094..a7b024d9 100644
--- a/src/routes/client/enable.rs
+++ b/src/routes/client/enable.rs
@@ -41,7 +41,7 @@ pub async fn enable_handler(
             Err("")
         }
     }
-    .map_err(|s| ErrorBadRequest(s))?; //todo
+    .map_err(|s| ErrorBadRequest(JsonResponse::<Client>::build().set_msg(s).to_string()))?;
 
     client.secret = client::generate_secret(pool.get_ref(), 255)
         .await
diff --git a/src/routes/client/update.rs b/src/routes/client/update.rs
index c833508d..34d7bfc5 100644
--- a/src/routes/client/update.rs
+++ b/src/routes/client/update.rs
@@ -40,12 +40,12 @@ pub async fn update_handler(
             Err("")
         }
     }
-    .map_err(|s| ErrorBadRequest(s))?; //todo
+    .map_err(|s| ErrorBadRequest(JsonResponse::<Client>::build().set_msg(s).to_string()))?;
 
     client.secret = client::generate_secret(pool.get_ref(), 255)
         .await
         .map(|s| Some(s))
-        .map_err(|s| ErrorBadRequest(s))?; //todo
+        .map_err(|s| ErrorBadRequest(JsonResponse::<Client>::build().set_msg(s).to_string()))?;
 
     let query_span = tracing::info_span!("Updating client into the database");
     match sqlx::query!(

From 53c8505c69f095273e9ffa732ef146dc71debdd7 Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 25 Nov 2023 06:29:33 +0200
Subject: [PATCH 010/284] issue-auth removed todo

---
 src/routes/test/deploy.rs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/routes/test/deploy.rs b/src/routes/test/deploy.rs
index 945e293e..7a8eddf0 100644
--- a/src/routes/test/deploy.rs
+++ b/src/routes/test/deploy.rs
@@ -9,7 +9,6 @@ struct DeployResponse {
     client: Arc<Client>,
 }
 
-//todo inject client through enpoint's inputs
 #[tracing::instrument(name = "Test deploy.")]
 #[post("/deploy")]
 pub async fn handler(client: web::ReqData<Arc<Client>>) -> Result<impl Responder> {

From c8a5ca2bfaf401b93cfae3b251f1be069d8fa0d0 Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 25 Nov 2023 17:22:30 +0200
Subject: [PATCH 011/284] issue-auth PayloadError

---
 src/middleware/client.rs | 26 ++++++++++----------------
 1 file changed, 10 insertions(+), 16 deletions(-)

diff --git a/src/middleware/client.rs b/src/middleware/client.rs
index 29931d6d..021f068c 100644
--- a/src/middleware/client.rs
+++ b/src/middleware/client.rs
@@ -122,20 +122,14 @@ where
                 }
             };
 
-            let content_length: usize =
-                get_header(&req, CONTENT_LENGTH.as_str()).map_err(|m| ErrorBadRequest(m))?;
-            let body = req
-                .take_payload()
-                .fold(
-                    BytesMut::with_capacity(content_length),
-                    |mut body, chunk| {
-                        let chunk = chunk.unwrap(); //todo process the potential error of unwrap
-                        body.extend_from_slice(&chunk); //todo
-
-                        ready(body)
-                    },
-                )
-                .await;
+            let content_length: usize = get_header(&req, CONTENT_LENGTH.as_str()).map_err(|m| {
+                ErrorBadRequest(JsonResponse::<Client>::build().set_msg(m).to_string())
+            })?;
+            let mut bytes = BytesMut::with_capacity(content_length);
+            let mut payload = req.take_payload();
+            while let Some(chunk) = payload.next().await {
+                bytes.extend_from_slice(&chunk?);
+            }
 
             let mut mac =
                 match Hmac::<Sha256>::new_from_slice(client.secret.as_ref().unwrap().as_bytes()) {
@@ -149,7 +143,7 @@ where
                     }
                 };
 
-            mac.update(body.as_ref());
+            mac.update(bytes.as_ref());
             let computed_hash = format!("{:x}", mac.finalize().into_bytes());
             if hash != computed_hash {
                 return Err(ErrorBadRequest(
@@ -160,7 +154,7 @@ where
             }
 
             let (_, mut payload) = actix_http::h1::Payload::create(true);
-            payload.unread_data(body.into());
+            payload.unread_data(bytes.into());
             req.set_payload(payload.into());
 
             match req.extensions_mut().insert(Arc::new(client)) {

From c562ddc8ea07dfa6bd7cd0e21478003bfeabfae0 Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sun, 26 Nov 2023 08:20:54 +0200
Subject: [PATCH 012/284] issue-auth console.rs

---
 Cargo.toml     | 6 +++++-
 src/console.rs | 3 +++
 2 files changed, 8 insertions(+), 1 deletion(-)
 create mode 100644 src/console.rs

diff --git a/Cargo.toml b/Cargo.toml
index 03017486..c046b34a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,7 +8,11 @@ path="src/lib.rs"
 
 [[bin]]
 path = "src/main.rs"
-name = "stacker"
+name = "server"
+
+[[bin]]
+path = "src/console.rs"
+name = "console"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
diff --git a/src/console.rs b/src/console.rs
new file mode 100644
index 00000000..47ad8c63
--- /dev/null
+++ b/src/console.rs
@@ -0,0 +1,3 @@
+fn main() {
+    println!("Hello World!");
+}

From e3c8dc8f4286b50f49f564cb889c837580df726e Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sun, 26 Nov 2023 08:33:38 +0200
Subject: [PATCH 013/284] issue-auth default-run

---
 Cargo.toml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Cargo.toml b/Cargo.toml
index c046b34a..66340ac7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,6 +2,7 @@
 name = "stacker"
 version = "0.1.0"
 edition = "2021"
+default-run= "server"
 
 [lib]
 path="src/lib.rs"

From 9643121f17bbe0a745e01f2637326413fd498dac Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sun, 26 Nov 2023 09:11:11 +0200
Subject: [PATCH 014/284] command

---
 Cargo.lock     | 101 +++++++++++++++++++++++++++++++++++++++++++++++++
 Cargo.toml     |   1 +
 src/console.rs |  39 ++++++++++++++++++-
 3 files changed, 140 insertions(+), 1 deletion(-)

diff --git a/Cargo.lock b/Cargo.lock
index 2ac84498..d4cbf7cc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -342,6 +342,54 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "anstream"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
+dependencies = [
+ "anstyle",
+ "windows-sys",
+]
+
 [[package]]
 name = "async-channel"
 version = "1.9.0"
@@ -644,6 +692,52 @@ dependencies = [
  "inout",
 ]
 
+[[package]]
+name = "clap"
+version = "4.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.38",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
+
 [[package]]
 name = "concurrent-queue"
 version = "2.3.0"
@@ -2750,6 +2844,7 @@ dependencies = [
  "actix-web",
  "actix-web-httpauth",
  "chrono",
+ "clap",
  "config",
  "derive_builder",
  "futures",
@@ -3219,6 +3314,12 @@ dependencies = [
  "percent-encoding",
 ]
 
+[[package]]
+name = "utf8parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
+
 [[package]]
 name = "uuid"
 version = "1.5.0"
diff --git a/Cargo.toml b/Cargo.toml
index 66340ac7..4c38d6ee 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -51,6 +51,7 @@ indexmap = { version = "2.0.0", features = ["serde"], optional = true }
 serde_yaml = "0.9"
 lapin = { version = "2.3.1", features = ["serde_json"] }
 futures-lite = "1.13.0"
+clap = { version = "4.4.8", features = ["derive"] }
 
 [dependencies.sqlx]
 version = "0.6.3"
diff --git a/src/console.rs b/src/console.rs
index 47ad8c63..8eed7579 100644
--- a/src/console.rs
+++ b/src/console.rs
@@ -1,3 +1,40 @@
+use clap::{Parser, Subcommand};
+
+#[derive(Parser, Debug)]
+struct Cli {
+    #[command(subcommand)]
+    command: Commands,
+}
+
+#[derive(Debug, Subcommand)]
+enum Commands {
+    AppClient {
+        #[command(subcommand)]
+        command: AppClientCommands,
+    },
+}
+
+#[derive(Debug, Subcommand)]
+enum AppClientCommands {
+    New {
+        #[arg(long)]
+        user_id: i32,
+    },
+}
+
 fn main() {
-    println!("Hello World!");
+    let cli = Cli::parse();
+
+    match cli.command {
+        Commands::AppClient { command } => {
+            process_app_client_command(command);
+        }
+        _ => {
+            println!("other variant");
+        }
+    };
+}
+
+fn process_app_client_command(command: AppClientCommands) {
+    println!("{command:?}");
 }

From 003e16c1f63b6b044d11740e389fe12d2aac815d Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sun, 26 Nov 2023 16:31:39 +0200
Subject: [PATCH 015/284] issue-auth removed client.id = 1

---
 src/routes/client/add.rs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/routes/client/add.rs b/src/routes/client/add.rs
index 9e722e35..8b136c11 100644
--- a/src/routes/client/add.rs
+++ b/src/routes/client/add.rs
@@ -50,7 +50,6 @@ pub async fn add_handler(
     };
 
     let mut client = Client::default();
-    client.id = 1;
     client.user_id = user.id.clone();
     client.secret = client::generate_secret(pool.get_ref(), 255)
         .await

From 5009fa36cb9a885b4a75ef6d5b1d1353fb872517 Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sun, 26 Nov 2023 17:59:18 +0200
Subject: [PATCH 016/284] issue-auth add_handler_internal

---
 src/routes/client/add.rs | 30 ++++++++++++++++++------------
 1 file changed, 18 insertions(+), 12 deletions(-)

diff --git a/src/routes/client/add.rs b/src/routes/client/add.rs
index 8b136c11..830095e2 100644
--- a/src/routes/client/add.rs
+++ b/src/routes/client/add.rs
@@ -1,9 +1,8 @@
 use crate::configuration::Settings;
 use crate::helpers::client;
 use crate::helpers::JsonResponse;
+use crate::models;
 use crate::models::user::User;
-use crate::models::Client;
-use actix_web::error::ErrorInternalServerError;
 use actix_web::{post, web, Responder, Result};
 use sqlx::PgPool;
 use std::sync::Arc;
@@ -16,6 +15,17 @@ pub async fn add_handler(
     settings: web::Data<Arc<Settings>>,
     pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
+    match add_handler_inner(user, settings, pool).await {
+        Ok(client) => JsonResponse::build().set_item(client).ok("Ok"),
+        Err(msg) => JsonResponse::build().bad_request(msg),
+    }
+}
+
+pub async fn add_handler_inner(
+    user: web::ReqData<User>,
+    settings: web::Data<Arc<Settings>>,
+    pool: web::Data<PgPool>,
+) -> Result<models::Client, String> {
     let query_span = tracing::info_span!("Counting the user's clients");
 
     match sqlx::query!(
@@ -40,23 +50,20 @@ pub async fn add_handler(
                     client_count
                 );
 
-                return JsonResponse::build().bad_request("Too many clients created");
+                return Err("Too many clients created".to_string());
             }
         }
         Err(e) => {
             tracing::error!("Failed to execute query: {:?}", e);
-            return JsonResponse::build().internal_server_error("Internal Server Error");
+            return Err("Internal Server Error".to_string());
         }
     };
 
-    let mut client = Client::default();
+    let mut client = models::Client::default();
     client.user_id = user.id.clone();
     client.secret = client::generate_secret(pool.get_ref(), 255)
         .await
-        .map(|s| Some(s))
-        .map_err(|s| {
-            ErrorInternalServerError(JsonResponse::<Client>::build().set_msg(s).to_string())
-        })?;
+        .map(|s| Some(s))?;
 
     let query_span = tracing::info_span!("Saving new client into the database");
     match sqlx::query!(
@@ -76,12 +83,11 @@ pub async fn add_handler(
             tracing::info!("New client {} have been saved to database", result.id);
             client.id = result.id;
 
-            return JsonResponse::build().set_item(Some(client)).ok("OK");
+            return Ok(client);
         }
         Err(e) => {
             tracing::error!("Failed to execute query: {:?}", e);
-            let err = format!("Failed to insert. {}", e);
-            return JsonResponse::build().bad_request(err);
+            return Err("Failed to insert".to_string());
         }
     }
 }

From e8fed13412f8f1307785494be889c764faa29959 Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sun, 26 Nov 2023 18:31:22 +0200
Subject: [PATCH 017/284] issue-auth

---
 src/routes/client/add.rs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/routes/client/add.rs b/src/routes/client/add.rs
index 830095e2..eeb00a1a 100644
--- a/src/routes/client/add.rs
+++ b/src/routes/client/add.rs
@@ -15,14 +15,14 @@ pub async fn add_handler(
     settings: web::Data<Arc<Settings>>,
     pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
-    match add_handler_inner(user, settings, pool).await {
+    match add_handler_inner(user.into_inner(), settings, pool).await {
         Ok(client) => JsonResponse::build().set_item(client).ok("Ok"),
         Err(msg) => JsonResponse::build().bad_request(msg),
     }
 }
 
 pub async fn add_handler_inner(
-    user: web::ReqData<User>,
+    user: User,
     settings: web::Data<Arc<Settings>>,
     pool: web::Data<PgPool>,
 ) -> Result<models::Client, String> {

From fa7f675ff8173fe2db3e469747ff16df4298484f Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sun, 26 Nov 2023 18:59:30 +0200
Subject: [PATCH 018/284] issue-auth console

---
 src/console.rs    | 48 +++++++++++++++++++++++++++++++++++++++++++----
 src/routes/mod.rs |  2 +-
 src/startup.rs    |  2 +-
 3 files changed, 46 insertions(+), 6 deletions(-)

diff --git a/src/console.rs b/src/console.rs
index 8eed7579..1ca6442b 100644
--- a/src/console.rs
+++ b/src/console.rs
@@ -1,4 +1,9 @@
+use actix_web::web;
 use clap::{Parser, Subcommand};
+use sqlx::PgPool;
+use stacker::configuration::get_configuration;
+use std::sync::Arc;
+use tokio::runtime::Runtime;
 
 #[derive(Parser, Debug)]
 struct Cli {
@@ -22,19 +27,54 @@ enum AppClientCommands {
     },
 }
 
-fn main() {
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+    /*
+    let db_pool = PgPool::connect(&settings.database.connection_string())
+        .await
+        .expect("Failed to connect to database.");
+    */
     let cli = Cli::parse();
 
     match cli.command {
         Commands::AppClient { command } => {
-            process_app_client_command(command);
+            process_app_client_command(command)?;
         }
         _ => {
             println!("other variant");
         }
     };
+
+    Ok(())
 }
 
-fn process_app_client_command(command: AppClientCommands) {
-    println!("{command:?}");
+fn process_app_client_command(
+    command: AppClientCommands,
+) -> Result<(), Box<dyn std::error::Error>> {
+    let rt = Runtime::new()?;
+
+    rt.block_on(async {
+        let settings = get_configuration().expect("Failed to read configuration.");
+        let db_pool = PgPool::connect(&settings.database.connection_string())
+            .await
+            .expect("Failed to connect to database.");
+
+        let settings = web::Data::new(Arc::new(settings)); //todo web::Data is already an Arc
+        let db_pool = web::Data::new(db_pool);
+
+        //todo get user from trydirect
+        let user = stacker::models::user::User {
+            id: "first_name".to_string(),
+            first_name: "first_name".to_string(),
+            last_name: "last_name".to_string(),
+            email: "email".to_string(),
+            email_confirmed: true,
+        };
+        stacker::routes::client::add_handler_inner(user, settings, db_pool)
+            .await
+            .expect("todo error"); //todo process the error
+        Ok(())
+    })
+
+    //rt.block_on(async {})
+    //Ok(())
 }
diff --git a/src/routes/mod.rs b/src/routes/mod.rs
index cab3e460..0d6e99e8 100644
--- a/src/routes/mod.rs
+++ b/src/routes/mod.rs
@@ -1,4 +1,4 @@
-pub(crate) mod client;
+pub mod client;
 pub mod health_checks;
 pub(crate) mod rating;
 pub(crate) mod test;
diff --git a/src/startup.rs b/src/startup.rs
index 3306fcd2..4b330b52 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -16,7 +16,7 @@ pub async fn run(
     db_pool: Pool<Postgres>,
     settings: Settings,
 ) -> Result<Server, std::io::Error> {
-    let settings = web::Data::new(Arc::new(settings));
+    let settings = web::Data::new(Arc::new(settings)); //todo web::Data is already an Arc
     let db_pool = web::Data::new(db_pool);
 
     // let address = format!("{}:{}", settings.app_host, settings.app_port);

From 613e905f13302005b5826724b797b44a955e3fbc Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Mon, 27 Nov 2023 17:11:51 +0200
Subject: [PATCH 019/284] issue-auth remove redundant Arc

---
 src/console.rs               | 5 +----
 src/middleware/trydirect.rs  | 3 +--
 src/routes/client/add.rs     | 5 ++---
 src/routes/client/disable.rs | 3 +--
 src/routes/client/enable.rs  | 3 +--
 src/routes/client/update.rs  | 2 +-
 src/routes/stack/deploy.rs   | 2 +-
 src/startup.rs               | 8 +-------
 8 files changed, 9 insertions(+), 22 deletions(-)

diff --git a/src/console.rs b/src/console.rs
index 1ca6442b..221154ab 100644
--- a/src/console.rs
+++ b/src/console.rs
@@ -58,7 +58,7 @@ fn process_app_client_command(
             .await
             .expect("Failed to connect to database.");
 
-        let settings = web::Data::new(Arc::new(settings)); //todo web::Data is already an Arc
+        let settings = web::Data::new(settings); //todo web::Data is already an Arc
         let db_pool = web::Data::new(db_pool);
 
         //todo get user from trydirect
@@ -74,7 +74,4 @@ fn process_app_client_command(
             .expect("todo error"); //todo process the error
         Ok(())
     })
-
-    //rt.block_on(async {})
-    //Ok(())
 }
diff --git a/src/middleware/trydirect.rs b/src/middleware/trydirect.rs
index 1d01c504..755baba2 100644
--- a/src/middleware/trydirect.rs
+++ b/src/middleware/trydirect.rs
@@ -7,7 +7,6 @@ use actix_web::Error;
 use actix_web::HttpMessage;
 use actix_web_httpauth::extractors::bearer::BearerAuth;
 use reqwest::header::{ACCEPT, CONTENT_TYPE};
-use std::sync::Arc;
 
 use crate::models::user::User;
 
@@ -16,7 +15,7 @@ pub async fn bearer_guard(
     req: ServiceRequest,
     credentials: BearerAuth,
 ) -> Result<ServiceRequest, (Error, ServiceRequest)> {
-    let settings = req.app_data::<web::Data<Arc<Settings>>>().unwrap();
+    let settings = req.app_data::<web::Data<Settings>>().unwrap();
     let client = reqwest::Client::new();
     let resp = client
         .get(&settings.auth_url)
diff --git a/src/routes/client/add.rs b/src/routes/client/add.rs
index eeb00a1a..db987057 100644
--- a/src/routes/client/add.rs
+++ b/src/routes/client/add.rs
@@ -5,14 +5,13 @@ use crate::models;
 use crate::models::user::User;
 use actix_web::{post, web, Responder, Result};
 use sqlx::PgPool;
-use std::sync::Arc;
 use tracing::Instrument;
 
 #[tracing::instrument(name = "Add client.")]
 #[post("")]
 pub async fn add_handler(
     user: web::ReqData<User>,
-    settings: web::Data<Arc<Settings>>,
+    settings: web::Data<Settings>,
     pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
     match add_handler_inner(user.into_inner(), settings, pool).await {
@@ -23,7 +22,7 @@ pub async fn add_handler(
 
 pub async fn add_handler_inner(
     user: User,
-    settings: web::Data<Arc<Settings>>,
+    settings: web::Data<Settings>,
     pool: web::Data<PgPool>,
 ) -> Result<models::Client, String> {
     let query_span = tracing::info_span!("Counting the user's clients");
diff --git a/src/routes/client/disable.rs b/src/routes/client/disable.rs
index f9a4a50e..b0d2cdeb 100644
--- a/src/routes/client/disable.rs
+++ b/src/routes/client/disable.rs
@@ -4,14 +4,13 @@ use crate::models::user::User;
 use crate::models::Client;
 use actix_web::{error::ErrorInternalServerError, put, web, Responder, Result};
 use sqlx::PgPool;
-use std::sync::Arc;
 use tracing::Instrument;
 
 #[tracing::instrument(name = "Disable client.")]
 #[put("/{id}/disable")]
 pub async fn disable_handler(
     user: web::ReqData<User>,
-    settings: web::Data<Arc<Settings>>,
+    settings: web::Data<Settings>,
     pool: web::Data<PgPool>,
     path: web::Path<(i32,)>,
 ) -> Result<impl Responder> {
diff --git a/src/routes/client/enable.rs b/src/routes/client/enable.rs
index a7b024d9..ef0548dc 100644
--- a/src/routes/client/enable.rs
+++ b/src/routes/client/enable.rs
@@ -5,14 +5,13 @@ use crate::models::user::User;
 use crate::models::Client;
 use actix_web::{error::ErrorBadRequest, put, web, Responder, Result};
 use sqlx::PgPool;
-use std::sync::Arc;
 use tracing::Instrument;
 
 #[tracing::instrument(name = "Enable client.")]
 #[put("/{id}/enable")]
 pub async fn enable_handler(
     user: web::ReqData<User>,
-    settings: web::Data<Arc<Settings>>,
+    settings: web::Data<Settings>,
     pool: web::Data<PgPool>,
     path: web::Path<(i32,)>,
 ) -> Result<impl Responder> {
diff --git a/src/routes/client/update.rs b/src/routes/client/update.rs
index 34d7bfc5..64604d30 100644
--- a/src/routes/client/update.rs
+++ b/src/routes/client/update.rs
@@ -11,7 +11,7 @@ use tracing::Instrument;
 #[put("/{id}")]
 pub async fn update_handler(
     user: web::ReqData<User>,
-    settings: web::Data<Arc<Settings>>,
+    settings: web::Data<Settings>,
     pool: web::Data<PgPool>,
     path: web::Path<(i32,)>,
 ) -> Result<impl Responder> {
diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs
index b76b63cf..5c9fa66f 100644
--- a/src/routes/stack/deploy.rs
+++ b/src/routes/stack/deploy.rs
@@ -18,7 +18,7 @@ pub async fn add(
     user: web::ReqData<User>,
     path: web::Path<(i32,)>,
     pool: Data<PgPool>,
-    sets: Data<Arc<Settings>>,
+    sets: Data<Settings>,
 ) -> Result<impl Responder> {
     let id = path.0;
     tracing::debug!("Received id: {}", id);
diff --git a/src/startup.rs b/src/startup.rs
index 4b330b52..7022c9c9 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -8,7 +8,6 @@ use actix_web::{
 use actix_web_httpauth::middleware::HttpAuthentication;
 use sqlx::{Pool, Postgres};
 use std::net::TcpListener;
-use std::sync::Arc;
 use tracing_actix_web::TracingLogger;
 
 pub async fn run(
@@ -16,14 +15,9 @@ pub async fn run(
     db_pool: Pool<Postgres>,
     settings: Settings,
 ) -> Result<Server, std::io::Error> {
-    let settings = web::Data::new(Arc::new(settings)); //todo web::Data is already an Arc
+    let settings = web::Data::new(settings);
     let db_pool = web::Data::new(db_pool);
 
-    // let address = format!("{}:{}", settings.app_host, settings.app_port);
-    // tracing::info!("Start server at {:?}", &address);
-    // let listener = std::net::TcpListener::bind(address)
-    //     .expect(&format!("failed to bind to {}", settings.app_port));
-
     let server = HttpServer::new(move || {
         App::new()
             .wrap(TracingLogger::default())

From 2ccb733165ad623f62fa6822aa2e100fede7927c Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Mon, 27 Nov 2023 18:07:44 +0200
Subject: [PATCH 020/284] issue-auth console

---
 src/console.rs | 62 +++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 54 insertions(+), 8 deletions(-)

diff --git a/src/console.rs b/src/console.rs
index 221154ab..d6a540e8 100644
--- a/src/console.rs
+++ b/src/console.rs
@@ -2,7 +2,6 @@ use actix_web::web;
 use clap::{Parser, Subcommand};
 use sqlx::PgPool;
 use stacker::configuration::get_configuration;
-use std::sync::Arc;
 use tokio::runtime::Runtime;
 
 #[derive(Parser, Debug)]
@@ -25,6 +24,46 @@ enum AppClientCommands {
         #[arg(long)]
         user_id: i32,
     },
+    New1 {
+        #[arg(long)]
+        user_id: i32,
+    },
+}
+
+trait StackerCommand {
+    fn call(&self) -> Result<(), Box<dyn std::error::Error>>;
+}
+
+struct AppClientNew {
+    user_id: i32,
+}
+
+struct AppClientNew1 {
+    user_id: i32,
+}
+
+impl AppClientNew1 {
+    fn new(user_id: i32) -> Self {
+        Self { user_id }
+    }
+}
+
+impl StackerCommand for AppClientNew1 {
+    fn call(&self) -> Result<(), Box<dyn std::error::Error>> {
+        Ok(())
+    }
+}
+
+impl AppClientNew {
+    fn new(user_id: i32) -> Self {
+        Self { user_id }
+    }
+}
+
+impl StackerCommand for AppClientNew {
+    fn call(&self) -> Result<(), Box<dyn std::error::Error>> {
+        Ok(())
+    }
 }
 
 fn main() -> Result<(), Box<dyn std::error::Error>> {
@@ -34,17 +73,24 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
         .expect("Failed to connect to database.");
     */
     let cli = Cli::parse();
+    println!("{cli:?}");
+
+    get_command(cli)?.call();
 
+    Ok(())
+}
+
+fn get_command(cli: Cli) -> Result<Box<dyn StackerCommand>, String> {
     match cli.command {
         Commands::AppClient { command } => {
-            process_app_client_command(command)?;
+            //process_app_client_command(command)?;
+            match command {
+                AppClientCommands::New { user_id } => Ok(Box::new(AppClientNew::new(user_id))),
+                AppClientCommands::New1 { user_id } => Ok(Box::new(AppClientNew1::new(user_id))),
+            }
         }
-        _ => {
-            println!("other variant");
-        }
-    };
-
-    Ok(())
+        _ => Err("command does not match".to_string()),
+    }
 }
 
 fn process_app_client_command(

From 279e29a9fa3e22b7a61a0c692662a8d4a3a4b057 Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Tue, 28 Nov 2023 09:18:42 +0200
Subject: [PATCH 021/284] issue-auth call thing

---
 src/console.rs | 64 ++++++++++++++++++++------------------------------
 1 file changed, 25 insertions(+), 39 deletions(-)

diff --git a/src/console.rs b/src/console.rs
index d6a540e8..8ace4e65 100644
--- a/src/console.rs
+++ b/src/console.rs
@@ -62,22 +62,37 @@ impl AppClientNew {
 
 impl StackerCommand for AppClientNew {
     fn call(&self) -> Result<(), Box<dyn std::error::Error>> {
-        Ok(())
+        let rt = Runtime::new()?;
+
+        rt.block_on(async {
+            let settings = get_configuration().expect("Failed to read configuration.");
+            let db_pool = PgPool::connect(&settings.database.connection_string())
+                .await
+                .expect("Failed to connect to database.");
+
+            let settings = web::Data::new(settings); //todo web::Data is already an Arc
+            let db_pool = web::Data::new(db_pool);
+
+            //todo get user from trydirect
+            let user = stacker::models::user::User {
+                id: "first_name".to_string(),
+                first_name: "first_name".to_string(),
+                last_name: "last_name".to_string(),
+                email: "email".to_string(),
+                email_confirmed: true,
+            };
+            stacker::routes::client::add_handler_inner(user, settings, db_pool)
+                .await
+                .expect("todo error"); //todo process the error
+            Ok(())
+        })
     }
 }
 
 fn main() -> Result<(), Box<dyn std::error::Error>> {
-    /*
-    let db_pool = PgPool::connect(&settings.database.connection_string())
-        .await
-        .expect("Failed to connect to database.");
-    */
     let cli = Cli::parse();
-    println!("{cli:?}");
-
-    get_command(cli)?.call();
 
-    Ok(())
+    get_command(cli)?.call()
 }
 
 fn get_command(cli: Cli) -> Result<Box<dyn StackerCommand>, String> {
@@ -92,32 +107,3 @@ fn get_command(cli: Cli) -> Result<Box<dyn StackerCommand>, String> {
         _ => Err("command does not match".to_string()),
     }
 }
-
-fn process_app_client_command(
-    command: AppClientCommands,
-) -> Result<(), Box<dyn std::error::Error>> {
-    let rt = Runtime::new()?;
-
-    rt.block_on(async {
-        let settings = get_configuration().expect("Failed to read configuration.");
-        let db_pool = PgPool::connect(&settings.database.connection_string())
-            .await
-            .expect("Failed to connect to database.");
-
-        let settings = web::Data::new(settings); //todo web::Data is already an Arc
-        let db_pool = web::Data::new(db_pool);
-
-        //todo get user from trydirect
-        let user = stacker::models::user::User {
-            id: "first_name".to_string(),
-            first_name: "first_name".to_string(),
-            last_name: "last_name".to_string(),
-            email: "email".to_string(),
-            email_confirmed: true,
-        };
-        stacker::routes::client::add_handler_inner(user, settings, db_pool)
-            .await
-            .expect("todo error"); //todo process the error
-        Ok(())
-    })
-}

From cd26401ab685543d27eb40266db5676e64114132 Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Tue, 28 Nov 2023 09:25:17 +0200
Subject: [PATCH 022/284] issue-auth rename console.rs

---
 Cargo.toml                          | 2 +-
 src/{console.rs => console/main.rs} | 0
 2 files changed, 1 insertion(+), 1 deletion(-)
 rename src/{console.rs => console/main.rs} (100%)

diff --git a/Cargo.toml b/Cargo.toml
index 4c38d6ee..f3d2357d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,7 +12,7 @@ path = "src/main.rs"
 name = "server"
 
 [[bin]]
-path = "src/console.rs"
+path = "src/console/main.rs"
 name = "console"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
diff --git a/src/console.rs b/src/console/main.rs
similarity index 100%
rename from src/console.rs
rename to src/console/main.rs

From 90d41aa43063fefe71e237bc9537e5ca86000084 Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Tue, 28 Nov 2023 09:56:51 +0200
Subject: [PATCH 023/284] issue-auth console command

---
 src/console/commands/appclient/mod.rs |  3 +
 src/console/commands/appclient/new.rs | 43 ++++++++++++++
 src/console/commands/callable.rs      |  3 +
 src/console/commands/mod.rs           |  4 ++
 src/console/main.rs                   | 82 +++------------------------
 src/console/mod.rs                    |  1 +
 src/lib.rs                            |  1 +
 7 files changed, 63 insertions(+), 74 deletions(-)
 create mode 100644 src/console/commands/appclient/mod.rs
 create mode 100644 src/console/commands/appclient/new.rs
 create mode 100644 src/console/commands/callable.rs
 create mode 100644 src/console/commands/mod.rs
 create mode 100644 src/console/mod.rs

diff --git a/src/console/commands/appclient/mod.rs b/src/console/commands/appclient/mod.rs
new file mode 100644
index 00000000..b6a00cdb
--- /dev/null
+++ b/src/console/commands/appclient/mod.rs
@@ -0,0 +1,3 @@
+mod new;
+
+pub use new::*;
diff --git a/src/console/commands/appclient/new.rs b/src/console/commands/appclient/new.rs
new file mode 100644
index 00000000..3f9763a7
--- /dev/null
+++ b/src/console/commands/appclient/new.rs
@@ -0,0 +1,43 @@
+use crate::configuration::get_configuration;
+use actix_web::web;
+use sqlx::PgPool;
+use tokio::runtime::Runtime; //todo actix_web runtime
+
+pub struct New {
+    user_id: i32,
+}
+
+impl New {
+    pub fn new(user_id: i32) -> Self {
+        Self { user_id }
+    }
+}
+
+impl crate::console::commands::CallableTrait for New {
+    fn call(&self) -> Result<(), Box<dyn std::error::Error>> {
+        let rt = Runtime::new()?;
+
+        rt.block_on(async {
+            let settings = get_configuration().expect("Failed to read configuration.");
+            let db_pool = PgPool::connect(&settings.database.connection_string())
+                .await
+                .expect("Failed to connect to database.");
+
+            let settings = web::Data::new(settings); //todo web::Data is already an Arc
+            let db_pool = web::Data::new(db_pool);
+
+            //todo get user from trydirect
+            let user = crate::models::user::User {
+                id: "first_name".to_string(),
+                first_name: "first_name".to_string(),
+                last_name: "last_name".to_string(),
+                email: "email".to_string(),
+                email_confirmed: true,
+            };
+            crate::routes::client::add_handler_inner(user, settings, db_pool)
+                .await
+                .expect("todo error"); //todo process the error
+            Ok(())
+        })
+    }
+}
diff --git a/src/console/commands/callable.rs b/src/console/commands/callable.rs
new file mode 100644
index 00000000..45e71240
--- /dev/null
+++ b/src/console/commands/callable.rs
@@ -0,0 +1,3 @@
+pub trait CallableTrait {
+    fn call(&self) -> Result<(), Box<dyn std::error::Error>>;
+}
diff --git a/src/console/commands/mod.rs b/src/console/commands/mod.rs
new file mode 100644
index 00000000..2cf75be7
--- /dev/null
+++ b/src/console/commands/mod.rs
@@ -0,0 +1,4 @@
+pub mod appclient;
+mod callable;
+
+pub use callable::*;
diff --git a/src/console/main.rs b/src/console/main.rs
index 8ace4e65..145d096d 100644
--- a/src/console/main.rs
+++ b/src/console/main.rs
@@ -1,8 +1,4 @@
-use actix_web::web;
 use clap::{Parser, Subcommand};
-use sqlx::PgPool;
-use stacker::configuration::get_configuration;
-use tokio::runtime::Runtime;
 
 #[derive(Parser, Debug)]
 struct Cli {
@@ -24,70 +20,10 @@ enum AppClientCommands {
         #[arg(long)]
         user_id: i32,
     },
-    New1 {
-        #[arg(long)]
-        user_id: i32,
-    },
-}
-
-trait StackerCommand {
-    fn call(&self) -> Result<(), Box<dyn std::error::Error>>;
-}
-
-struct AppClientNew {
-    user_id: i32,
-}
-
-struct AppClientNew1 {
-    user_id: i32,
-}
-
-impl AppClientNew1 {
-    fn new(user_id: i32) -> Self {
-        Self { user_id }
-    }
 }
 
-impl StackerCommand for AppClientNew1 {
-    fn call(&self) -> Result<(), Box<dyn std::error::Error>> {
-        Ok(())
-    }
-}
-
-impl AppClientNew {
-    fn new(user_id: i32) -> Self {
-        Self { user_id }
-    }
-}
-
-impl StackerCommand for AppClientNew {
-    fn call(&self) -> Result<(), Box<dyn std::error::Error>> {
-        let rt = Runtime::new()?;
-
-        rt.block_on(async {
-            let settings = get_configuration().expect("Failed to read configuration.");
-            let db_pool = PgPool::connect(&settings.database.connection_string())
-                .await
-                .expect("Failed to connect to database.");
-
-            let settings = web::Data::new(settings); //todo web::Data is already an Arc
-            let db_pool = web::Data::new(db_pool);
-
-            //todo get user from trydirect
-            let user = stacker::models::user::User {
-                id: "first_name".to_string(),
-                first_name: "first_name".to_string(),
-                last_name: "last_name".to_string(),
-                email: "email".to_string(),
-                email_confirmed: true,
-            };
-            stacker::routes::client::add_handler_inner(user, settings, db_pool)
-                .await
-                .expect("todo error"); //todo process the error
-            Ok(())
-        })
-    }
-}
+//todo add documentation about how to add a new command
+//todo the helper from console should have a nicer display
 
 fn main() -> Result<(), Box<dyn std::error::Error>> {
     let cli = Cli::parse();
@@ -95,15 +31,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
     get_command(cli)?.call()
 }
 
-fn get_command(cli: Cli) -> Result<Box<dyn StackerCommand>, String> {
+fn get_command(cli: Cli) -> Result<Box<dyn stacker::console::commands::CallableTrait>, String> {
     match cli.command {
-        Commands::AppClient { command } => {
-            //process_app_client_command(command)?;
-            match command {
-                AppClientCommands::New { user_id } => Ok(Box::new(AppClientNew::new(user_id))),
-                AppClientCommands::New1 { user_id } => Ok(Box::new(AppClientNew1::new(user_id))),
-            }
-        }
+        Commands::AppClient { command } => match command {
+            AppClientCommands::New { user_id } => Ok(Box::new(
+                stacker::console::commands::appclient::New::new(user_id),
+            )),
+        },
         _ => Err("command does not match".to_string()),
     }
 }
diff --git a/src/console/mod.rs b/src/console/mod.rs
new file mode 100644
index 00000000..82b6da3c
--- /dev/null
+++ b/src/console/mod.rs
@@ -0,0 +1 @@
+pub mod commands;
diff --git a/src/lib.rs b/src/lib.rs
index 56bd1f02..37e4fae9 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,4 +1,5 @@
 pub mod configuration;
+pub mod console;
 pub mod forms;
 pub mod helpers;
 mod middleware;

From f5f6a07a648a8424ea2425fb725789222315649b Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Tue, 28 Nov 2023 18:12:47 +0200
Subject: [PATCH 024/284] issue-auth actix runtime

---
 src/console/commands/appclient/new.rs | 19 ++++++++-----------
 src/console/main.rs                   |  2 +-
 2 files changed, 9 insertions(+), 12 deletions(-)

diff --git a/src/console/commands/appclient/new.rs b/src/console/commands/appclient/new.rs
index 3f9763a7..138f11f7 100644
--- a/src/console/commands/appclient/new.rs
+++ b/src/console/commands/appclient/new.rs
@@ -1,29 +1,27 @@
 use crate::configuration::get_configuration;
+use actix_web::rt;
 use actix_web::web;
 use sqlx::PgPool;
-use tokio::runtime::Runtime; //todo actix_web runtime
 
-pub struct New {
+pub struct NewCommand {
     user_id: i32,
 }
 
-impl New {
+impl NewCommand {
     pub fn new(user_id: i32) -> Self {
         Self { user_id }
     }
 }
 
-impl crate::console::commands::CallableTrait for New {
+impl crate::console::commands::CallableTrait for NewCommand {
     fn call(&self) -> Result<(), Box<dyn std::error::Error>> {
-        let rt = Runtime::new()?;
-
-        rt.block_on(async {
+        rt::System::new().block_on(async {
             let settings = get_configuration().expect("Failed to read configuration.");
             let db_pool = PgPool::connect(&settings.database.connection_string())
                 .await
                 .expect("Failed to connect to database.");
 
-            let settings = web::Data::new(settings); //todo web::Data is already an Arc
+            let settings = web::Data::new(settings);
             let db_pool = web::Data::new(db_pool);
 
             //todo get user from trydirect
@@ -34,9 +32,8 @@ impl crate::console::commands::CallableTrait for New {
                 email: "email".to_string(),
                 email_confirmed: true,
             };
-            crate::routes::client::add_handler_inner(user, settings, db_pool)
-                .await
-                .expect("todo error"); //todo process the error
+            crate::routes::client::add_handler_inner(user, settings, db_pool).await?;
+
             Ok(())
         })
     }
diff --git a/src/console/main.rs b/src/console/main.rs
index 145d096d..09211286 100644
--- a/src/console/main.rs
+++ b/src/console/main.rs
@@ -35,7 +35,7 @@ fn get_command(cli: Cli) -> Result<Box<dyn stacker::console::commands::CallableT
     match cli.command {
         Commands::AppClient { command } => match command {
             AppClientCommands::New { user_id } => Ok(Box::new(
-                stacker::console::commands::appclient::New::new(user_id),
+                stacker::console::commands::appclient::NewCommand::new(user_id),
             )),
         },
         _ => Err("command does not match".to_string()),

From cda917bbd82399812c7c8ef25e8071159f17518a Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Tue, 28 Nov 2023 18:38:39 +0200
Subject: [PATCH 025/284] issue-auth

---
 src/middleware/trydirect.rs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/middleware/trydirect.rs b/src/middleware/trydirect.rs
index 755baba2..e19b57a9 100644
--- a/src/middleware/trydirect.rs
+++ b/src/middleware/trydirect.rs
@@ -60,6 +60,7 @@ pub async fn bearer_guard(
         tracing::error!("already logged {existent_user:?}");
         return Err((ErrorInternalServerError(""), req));
     }
+    //todo move request outside
 
     Ok(req)
 }

From f2ac4b1194fc213a815574210e2d6d0016ec58b8 Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Wed, 29 Nov 2023 21:18:57 +0200
Subject: [PATCH 026/284] issue-auth

---
 src/middleware/trydirect.rs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/middleware/trydirect.rs b/src/middleware/trydirect.rs
index e19b57a9..755baba2 100644
--- a/src/middleware/trydirect.rs
+++ b/src/middleware/trydirect.rs
@@ -60,7 +60,6 @@ pub async fn bearer_guard(
         tracing::error!("already logged {existent_user:?}");
         return Err((ErrorInternalServerError(""), req));
     }
-    //todo move request outside
 
     Ok(req)
 }

From 507dc5695969379d7990056ea653d13e016796bc Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Fri, 1 Dec 2023 17:26:21 +0200
Subject: [PATCH 027/284] issue-auth middleware/client optimisation

---
 src/middleware/client.rs | 68 ++++++++++++++--------------------------
 1 file changed, 24 insertions(+), 44 deletions(-)

diff --git a/src/middleware/client.rs b/src/middleware/client.rs
index 021f068c..4309ac51 100644
--- a/src/middleware/client.rs
+++ b/src/middleware/client.rs
@@ -74,12 +74,8 @@ where
     fn call(&self, mut req: ServiceRequest) -> Self::Future {
         let service = self.service.clone();
         async move {
-            let client_id: i32 = get_header(&req, "stacker-id").map_err(|m| {
-                ErrorBadRequest(JsonResponse::<Client>::build().set_msg(m).to_string())
-            })?;
-            let hash: String = get_header(&req, "stacker-hash").map_err(|m| {
-                ErrorBadRequest(JsonResponse::<Client>::build().set_msg(m).to_string())
-            })?;
+            let client_id: i32 = get_header(&req, "stacker-id")?;
+            let hash: String = get_header(&req, "stacker-hash")?;
 
             let query_span = tracing::info_span!("Fetching the client by ID");
             let db_pool = req.app_data::<web::Data<Pool<Postgres>>>().unwrap();
@@ -98,37 +94,20 @@ where
             .instrument(query_span)
             .await
             {
-                Ok(client) if client.secret.is_some() => client,
-                Ok(_client) => {
-                    return Err(ErrorForbidden(
-                        JsonResponse::<Client>::build()
-                            .set_msg("client is not active")
-                            .to_string(),
-                    ));
-                }
-                Err(sqlx::Error::RowNotFound) => {
-                    return Err(ErrorNotFound(
-                        JsonResponse::<Client>::build()
-                            .set_msg("the client is not found")
-                            .to_string(),
-                    ));
-                }
+                Ok(client) if client.secret.is_some() => Ok(client),
+                Ok(_client) => Err("client is not active".to_string()),
+                Err(sqlx::Error::RowNotFound) => Err("the client is not found".to_string()),
                 Err(e) => {
                     tracing::error!("Failed to execute fetch query: {:?}", e);
-
-                    return Err(ErrorInternalServerError(
-                        JsonResponse::<Client>::build().to_string(),
-                    ));
+                    Err("".to_string())
                 }
-            };
+            }?;
 
-            let content_length: usize = get_header(&req, CONTENT_LENGTH.as_str()).map_err(|m| {
-                ErrorBadRequest(JsonResponse::<Client>::build().set_msg(m).to_string())
-            })?;
+            let content_length: usize = get_header(&req, CONTENT_LENGTH.as_str())?;
             let mut bytes = BytesMut::with_capacity(content_length);
             let mut payload = req.take_payload();
             while let Some(chunk) = payload.next().await {
-                bytes.extend_from_slice(&chunk?);
+                bytes.extend_from_slice(&chunk.expect("can't unwrap the chunk"));
             }
 
             let mut mac =
@@ -136,21 +115,14 @@ where
                     Ok(mac) => mac,
                     Err(err) => {
                         tracing::error!("error generating hmac {err:?}");
-
-                        return Err(ErrorInternalServerError(
-                            JsonResponse::<Client>::build().to_string(),
-                        ));
+                        return Err("".to_string());
                     }
                 };
 
             mac.update(bytes.as_ref());
             let computed_hash = format!("{:x}", mac.finalize().into_bytes());
             if hash != computed_hash {
-                return Err(ErrorBadRequest(
-                    JsonResponse::<Client>::build()
-                        .set_msg("hash is wrong")
-                        .to_string(),
-                ));
+                return Err("hash is wrong".to_string());
             }
 
             let (_, mut payload) = actix_http::h1::Payload::create(true);
@@ -160,16 +132,24 @@ where
             match req.extensions_mut().insert(Arc::new(client)) {
                 Some(_) => {
                     tracing::error!("client middleware already called once");
-                    return Err(ErrorInternalServerError(
-                        JsonResponse::<Client>::build().to_string(),
-                    ));
+                    return Err("".to_string());
                 }
                 None => {}
             }
 
-            let service = service.lock().await;
-            service.call(req).await
+            Ok(req)
         }
+        .then(|req| async move {
+            match req {
+                Ok(req) => {
+                    let service = service.lock().await;
+                    service.call(req).await
+                }
+                Err(msg) => Err(ErrorBadRequest(
+                    JsonResponse::<Client>::build().set_msg(msg).to_string(),
+                )),
+            }
+        })
         .boxed_local()
     }
 }

From caee9a4253e3fe1a79b02cd8dcb308a92a1b368f Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 2 Dec 2023 17:59:07 +0200
Subject: [PATCH 028/284] issue-auth tests/common.rs

---
 tests/common.rs       |  83 ++++++++++++++++++++++++++
 tests/health_check.rs | 133 +-----------------------------------------
 2 files changed, 85 insertions(+), 131 deletions(-)
 create mode 100644 tests/common.rs

diff --git a/tests/common.rs b/tests/common.rs
new file mode 100644
index 00000000..afc41343
--- /dev/null
+++ b/tests/common.rs
@@ -0,0 +1,83 @@
+use actix_web::{get, web, App, HttpServer, Responder};
+use sqlx::{Connection, Executor, PgConnection, PgPool};
+use stacker::configuration::{get_configuration, DatabaseSettings};
+use stacker::forms;
+use std::net::TcpListener;
+
+pub async fn spawn_app() -> TestApp {
+    let mut configuration = get_configuration().expect("Failed to get configuration");
+
+    let listener = std::net::TcpListener::bind("127.0.0.1:0")
+        .expect("Failed to bind port for testing auth server");
+
+    configuration.auth_url = format!(
+        "http://127.0.0.1:{}/me",
+        listener.local_addr().unwrap().port()
+    );
+    println!("Auth Server is running on: {}", configuration.auth_url);
+
+    let handle = tokio::spawn(mock_auth_server(listener));
+    handle.await.expect("Auth Server can not be started");
+
+    let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("Failed to bind random port");
+
+    let port = listener.local_addr().unwrap().port();
+    let address = format!("http://127.0.0.1:{}", port);
+    configuration.database.database_name = uuid::Uuid::new_v4().to_string();
+
+    let connection_pool = configure_database(&configuration.database).await;
+
+    let server = stacker::startup::run(listener, connection_pool.clone(), configuration)
+        .await
+        .expect("Failed to bind address.");
+
+    let _ = tokio::spawn(server);
+    println!("Used Port: {}", port);
+
+    TestApp {
+        address,
+        db_pool: connection_pool,
+    }
+}
+
+pub async fn configure_database(config: &DatabaseSettings) -> PgPool {
+    let mut connection = PgConnection::connect(&config.connection_string_without_db())
+        .await
+        .expect("Failed to connect to postgres");
+
+    connection
+        .execute(format!(r#"CREATE DATABASE "{}""#, config.database_name).as_str())
+        .await
+        .expect("Failed to create database");
+
+    let connection_pool = PgPool::connect(&config.connection_string())
+        .await
+        .expect("Failed to connect to database pool");
+
+    sqlx::migrate!("./migrations")
+        .run(&connection_pool)
+        .await
+        .expect("Failed to migrate database");
+
+    connection_pool
+}
+
+pub struct TestApp {
+    pub address: String,
+    pub db_pool: PgPool,
+}
+
+#[get("")]
+async fn mock_auth() -> actix_web::Result<impl Responder> {
+    println!("Starting auth server in test mode ...");
+    // 1. set user id
+    // 2. add token to header / hardcoded
+    Ok(web::Json(forms::user::UserForm::default()))
+}
+
+async fn mock_auth_server(listener: TcpListener) -> actix_web::dev::Server {
+    HttpServer::new(|| App::new().service(web::scope("/me").service(mock_auth)))
+        .listen(listener)
+        .unwrap()
+        .run()
+}
diff --git a/tests/health_check.rs b/tests/health_check.rs
index 0b858bf8..1496735a 100644
--- a/tests/health_check.rs
+++ b/tests/health_check.rs
@@ -1,31 +1,4 @@
-//#[actix_rt::test]
-
-use std::net::TcpListener;
-use actix_web::{App, HttpServer, web, Responder, get};
-use sqlx::{Connection, Executor, PgConnection, PgPool};
-use stacker::configuration::{get_configuration, DatabaseSettings};
-use stacker::forms;
-
-
-#[get("")]
-async fn mock_auth() -> actix_web::Result<impl Responder> {
-    println!("Starting auth server in test mode ...");
-    // 1. set user id
-    // 2. add token to header / hardcoded
-    Ok(web::Json(forms::user::UserForm::default()))
-}
-
-async fn mock_auth_server(listener:TcpListener) -> actix_web::dev::Server {
-
-    HttpServer::new(|| {
-        App::new()
-            .service(web::scope("/me")
-                .service(mock_auth))
-        })
-        .listen(listener)
-        .unwrap()
-        .run()
-}
+mod common;
 
 #[tokio::test]
 async fn health_check_works() {
@@ -34,7 +7,7 @@ async fn health_check_works() {
     // 3. Assert
 
     println!("Before spawn_app");
-    let app = spawn_app().await; // server
+    let app = common::spawn_app().await; // server
     println!("After spawn_app");
     let client = reqwest::Client::new(); // client
 
@@ -46,106 +19,4 @@ async fn health_check_works() {
 
     assert!(response.status().is_success());
     assert_eq!(Some(0), response.content_length());
-    // let app = App::new().service(web::resource("/health_check").route(web::get().to(health_check)));
-    // let mut app = test::init_service(app).await;
-    // let req = test::TestRequest::get().uri("/health_check").to_request();
-    // let resp = test::call_service(&mut app, req).await;
-    // assert_eq!(resp.status(), StatusCode::OK);
-}
-
-// test that locks main thread
-// async fn spawn_app() -> std::io::Result<()> {
-//     stacker::run().await
-// }
-
-pub struct TestApp {
-    pub address: String,
-    pub db_pool: PgPool,
-}
-
-pub async fn configure_database(config: &DatabaseSettings) -> PgPool {
-    let mut connection = PgConnection::connect(&config.connection_string_without_db())
-        .await
-        .expect("Failed to connect to postgres");
-
-    connection
-        .execute(format!(r#"CREATE DATABASE "{}""#, config.database_name).as_str())
-        .await
-        .expect("Failed to create database");
-
-    let connection_pool = PgPool::connect(&config.connection_string())
-        .await
-        .expect("Failed to connect to database pool");
-
-    sqlx::migrate!("./migrations")
-        .run(&connection_pool)
-        .await
-        .expect("Failed to migrate database");
-
-    connection_pool
-}
-
-
-// we have to run server in another thread
-async fn spawn_app() -> TestApp {
-    // Future<TestApp>
-
-    // let mut rt = tokio::runtime::Runtime::new().unwrap();
-    // rt.spawn(mock_auth_server(listener)).expect("Could not spawn auth server");
-    let mut configuration = get_configuration().expect("Failed to get configuration");
-
-    let listener = std::net::TcpListener::bind("127.0.0.1:0")
-        .expect("Failed to bind port for testing auth server");
-
-    configuration.auth_url = format!("http://127.0.0.1:{}/me", listener.local_addr().unwrap().port());
-    println!("Auth Server is running on: {}", configuration.auth_url);
-
-    let handle = tokio::spawn(mock_auth_server(listener));
-    handle.await.expect("Auth Server can not be started");
-
-    let listener = std::net::TcpListener::bind("127.0.0.1:0")
-        .expect("Failed to bind random port");
-
-    let port = listener.local_addr().unwrap().port();
-    let address = format!("http://127.0.0.1:{}", port);
-    configuration.database.database_name = uuid::Uuid::new_v4().to_string();
-
-    let connection_pool = configure_database(&configuration.database).await;
-
-    let server = stacker::startup::run(listener, connection_pool.clone(), configuration)
-        .await.expect("Failed to bind address.");
-
-    let _ = tokio::spawn(server);
-    println!("Used Port: {}", port);
-
-    TestApp {
-        address,
-        db_pool: connection_pool,
-    }
 }
-
-#[tokio::test]
-async fn add_rating_returns_a_200_for_valid_form_data() {
-    // Arrange
-    let app = spawn_app().await;
-    let client = reqwest::Client::new();
-
-    // let body = "name=le%20guin&email=ursula_le_guin%40gmail.com"; // %20 - space, %40 - @
-    // let response = client
-    //     .post(&format!("{}/subscriptions", &app.address))
-    //     .header("Content-Type", "application/x-www-form-urlencoded")
-    //     .body(body)
-    //     .send()
-    //     .await
-    //     .expect("Failed to execute request.");
-    //
-    // assert_eq!(200, response.status().as_u16());
-    //
-    // let saved = sqlx::query!("SELECT email, name FROM subscriptions",)
-    //     .fetch_one(&app.db_pool)
-    //     .await
-    //     .expect("Failed to fetch saved subscription.");
-    //
-    // assert_eq!(saved.email, "ursula_le_guin@gmail.com");
-    // assert_eq!(saved.name, "le guin");
-}
\ No newline at end of file

From f62296cb40ed7116fdb7e54fc5c948469a2157e0 Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 2 Dec 2023 18:10:32 +0200
Subject: [PATCH 029/284] issue-auth no common test target

---
 tests/common.rs | 83 -------------------------------------------------
 1 file changed, 83 deletions(-)
 delete mode 100644 tests/common.rs

diff --git a/tests/common.rs b/tests/common.rs
deleted file mode 100644
index afc41343..00000000
--- a/tests/common.rs
+++ /dev/null
@@ -1,83 +0,0 @@
-use actix_web::{get, web, App, HttpServer, Responder};
-use sqlx::{Connection, Executor, PgConnection, PgPool};
-use stacker::configuration::{get_configuration, DatabaseSettings};
-use stacker::forms;
-use std::net::TcpListener;
-
-pub async fn spawn_app() -> TestApp {
-    let mut configuration = get_configuration().expect("Failed to get configuration");
-
-    let listener = std::net::TcpListener::bind("127.0.0.1:0")
-        .expect("Failed to bind port for testing auth server");
-
-    configuration.auth_url = format!(
-        "http://127.0.0.1:{}/me",
-        listener.local_addr().unwrap().port()
-    );
-    println!("Auth Server is running on: {}", configuration.auth_url);
-
-    let handle = tokio::spawn(mock_auth_server(listener));
-    handle.await.expect("Auth Server can not be started");
-
-    let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("Failed to bind random port");
-
-    let port = listener.local_addr().unwrap().port();
-    let address = format!("http://127.0.0.1:{}", port);
-    configuration.database.database_name = uuid::Uuid::new_v4().to_string();
-
-    let connection_pool = configure_database(&configuration.database).await;
-
-    let server = stacker::startup::run(listener, connection_pool.clone(), configuration)
-        .await
-        .expect("Failed to bind address.");
-
-    let _ = tokio::spawn(server);
-    println!("Used Port: {}", port);
-
-    TestApp {
-        address,
-        db_pool: connection_pool,
-    }
-}
-
-pub async fn configure_database(config: &DatabaseSettings) -> PgPool {
-    let mut connection = PgConnection::connect(&config.connection_string_without_db())
-        .await
-        .expect("Failed to connect to postgres");
-
-    connection
-        .execute(format!(r#"CREATE DATABASE "{}""#, config.database_name).as_str())
-        .await
-        .expect("Failed to create database");
-
-    let connection_pool = PgPool::connect(&config.connection_string())
-        .await
-        .expect("Failed to connect to database pool");
-
-    sqlx::migrate!("./migrations")
-        .run(&connection_pool)
-        .await
-        .expect("Failed to migrate database");
-
-    connection_pool
-}
-
-pub struct TestApp {
-    pub address: String,
-    pub db_pool: PgPool,
-}
-
-#[get("")]
-async fn mock_auth() -> actix_web::Result<impl Responder> {
-    println!("Starting auth server in test mode ...");
-    // 1. set user id
-    // 2. add token to header / hardcoded
-    Ok(web::Json(forms::user::UserForm::default()))
-}
-
-async fn mock_auth_server(listener: TcpListener) -> actix_web::dev::Server {
-    HttpServer::new(|| App::new().service(web::scope("/me").service(mock_auth)))
-        .listen(listener)
-        .unwrap()
-        .run()
-}

From 38da8d42a760db39195bab33c909d8cef1d732fd Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 2 Dec 2023 18:11:05 +0200
Subject: [PATCH 030/284] issue-auth no common test target

---
 tests/common/mod.rs | 83 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 83 insertions(+)
 create mode 100644 tests/common/mod.rs

diff --git a/tests/common/mod.rs b/tests/common/mod.rs
new file mode 100644
index 00000000..afc41343
--- /dev/null
+++ b/tests/common/mod.rs
@@ -0,0 +1,83 @@
+use actix_web::{get, web, App, HttpServer, Responder};
+use sqlx::{Connection, Executor, PgConnection, PgPool};
+use stacker::configuration::{get_configuration, DatabaseSettings};
+use stacker::forms;
+use std::net::TcpListener;
+
+pub async fn spawn_app() -> TestApp {
+    let mut configuration = get_configuration().expect("Failed to get configuration");
+
+    let listener = std::net::TcpListener::bind("127.0.0.1:0")
+        .expect("Failed to bind port for testing auth server");
+
+    configuration.auth_url = format!(
+        "http://127.0.0.1:{}/me",
+        listener.local_addr().unwrap().port()
+    );
+    println!("Auth Server is running on: {}", configuration.auth_url);
+
+    let handle = tokio::spawn(mock_auth_server(listener));
+    handle.await.expect("Auth Server can not be started");
+
+    let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("Failed to bind random port");
+
+    let port = listener.local_addr().unwrap().port();
+    let address = format!("http://127.0.0.1:{}", port);
+    configuration.database.database_name = uuid::Uuid::new_v4().to_string();
+
+    let connection_pool = configure_database(&configuration.database).await;
+
+    let server = stacker::startup::run(listener, connection_pool.clone(), configuration)
+        .await
+        .expect("Failed to bind address.");
+
+    let _ = tokio::spawn(server);
+    println!("Used Port: {}", port);
+
+    TestApp {
+        address,
+        db_pool: connection_pool,
+    }
+}
+
+pub async fn configure_database(config: &DatabaseSettings) -> PgPool {
+    let mut connection = PgConnection::connect(&config.connection_string_without_db())
+        .await
+        .expect("Failed to connect to postgres");
+
+    connection
+        .execute(format!(r#"CREATE DATABASE "{}""#, config.database_name).as_str())
+        .await
+        .expect("Failed to create database");
+
+    let connection_pool = PgPool::connect(&config.connection_string())
+        .await
+        .expect("Failed to connect to database pool");
+
+    sqlx::migrate!("./migrations")
+        .run(&connection_pool)
+        .await
+        .expect("Failed to migrate database");
+
+    connection_pool
+}
+
+pub struct TestApp {
+    pub address: String,
+    pub db_pool: PgPool,
+}
+
+#[get("")]
+async fn mock_auth() -> actix_web::Result<impl Responder> {
+    println!("Starting auth server in test mode ...");
+    // 1. set user id
+    // 2. add token to header / hardcoded
+    Ok(web::Json(forms::user::UserForm::default()))
+}
+
+async fn mock_auth_server(listener: TcpListener) -> actix_web::dev::Server {
+    HttpServer::new(|| App::new().service(web::scope("/me").service(mock_auth)))
+        .listen(listener)
+        .unwrap()
+        .run()
+}

From 14a8a367c89578d4a111e5af17938cb6e081424e Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 2 Dec 2023 18:15:58 +0200
Subject: [PATCH 031/284] issue-auth test middleware_client sketch

---
 tests/middleware_client.rs | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)
 create mode 100644 tests/middleware_client.rs

diff --git a/tests/middleware_client.rs b/tests/middleware_client.rs
new file mode 100644
index 00000000..31c55653
--- /dev/null
+++ b/tests/middleware_client.rs
@@ -0,0 +1,22 @@
+mod common;
+
+#[tokio::test]
+async fn middleware_client_works() {
+    // 1. Arrange
+    // 2. Act
+    // 3. Assert
+
+    println!("Before spawn_app");
+    let app = common::spawn_app().await; // server
+    println!("After spawn_app");
+    let client = reqwest::Client::new(); // client
+
+    let response = client
+        .get(&format!("{}/health_check", &app.address))
+        .send()
+        .await
+        .expect("Failed to execute request.");
+
+    assert!(response.status().is_success());
+    assert_eq!(Some(0), response.content_length());
+}

From a565c4ebf242bb0a3e5416546fd9c2836897e6ae Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Sun, 3 Dec 2023 15:46:02 +0200
Subject: [PATCH 032/284] ports deserialize fix

---
 Cargo.lock                   | 665 +++++++++++++++++++++++------------
 src/forms/mod.rs             |   2 +-
 src/forms/stack.rs           |  83 +++--
 src/helpers/json.rs          |  58 ---
 src/helpers/stack/builder.rs | 274 +++++++++------
 src/middleware/trydirect.rs  |   2 +-
 src/routes/stack/add.rs      |   9 +-
 src/routes/stack/compose.rs  |  19 +-
 tests/app.json               |  99 ++++++
 tests/model_user_stack.rs    |  62 ++++
 tests/web-item.json          |  99 ++++++
 11 files changed, 931 insertions(+), 441 deletions(-)
 create mode 100644 tests/app.json
 create mode 100644 tests/model_user_stack.rs
 create mode 100644 tests/web-item.json

diff --git a/Cargo.lock b/Cargo.lock
index 2ac84498..abfde51c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -44,8 +44,8 @@ dependencies = [
  "actix-rt",
  "actix-service",
  "actix-utils",
- "ahash 0.8.3",
- "base64 0.21.4",
+ "ahash 0.8.6",
+ "base64 0.21.5",
  "bitflags 2.4.1",
  "brotli",
  "bytes",
@@ -80,7 +80,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb"
 dependencies = [
  "quote",
- "syn 2.0.38",
+ "syn 2.0.39",
 ]
 
 [[package]]
@@ -118,7 +118,7 @@ dependencies = [
  "futures-core",
  "futures-util",
  "mio",
- "socket2 0.5.4",
+ "socket2 0.5.5",
  "tokio",
  "tracing",
 ]
@@ -159,7 +159,7 @@ dependencies = [
  "actix-service",
  "actix-utils",
  "actix-web-codegen",
- "ahash 0.8.3",
+ "ahash 0.8.6",
  "bytes",
  "bytestring",
  "cfg-if",
@@ -179,7 +179,7 @@ dependencies = [
  "serde_json",
  "serde_urlencoded",
  "smallvec",
- "socket2 0.5.4",
+ "socket2 0.5.5",
  "time 0.3.30",
  "url",
 ]
@@ -193,7 +193,7 @@ dependencies = [
  "actix-router",
  "proc-macro2",
  "quote",
- "syn 2.0.38",
+ "syn 2.0.39",
 ]
 
 [[package]]
@@ -204,7 +204,7 @@ checksum = "1d613edf08a42ccc6864c941d30fe14e1b676a77d16f1dbadc1174d065a0a775"
 dependencies = [
  "actix-utils",
  "actix-web",
- "base64 0.21.4",
+ "base64 0.21.5",
  "futures-core",
  "futures-util",
  "log",
@@ -228,9 +228,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
 
 [[package]]
 name = "ahash"
-version = "0.7.6"
+version = "0.7.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
+checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd"
 dependencies = [
  "getrandom",
  "once_cell",
@@ -239,14 +239,15 @@ dependencies = [
 
 [[package]]
 name = "ahash"
-version = "0.8.3"
+version = "0.8.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
+checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
 dependencies = [
  "cfg-if",
  "getrandom",
  "once_cell",
  "version_check",
+ "zerocopy",
 ]
 
 [[package]]
@@ -344,41 +345,43 @@ dependencies = [
 
 [[package]]
 name = "async-channel"
-version = "1.9.0"
+version = "2.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
+checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c"
 dependencies = [
  "concurrent-queue",
- "event-listener",
+ "event-listener 4.0.0",
+ "event-listener-strategy",
  "futures-core",
+ "pin-project-lite",
 ]
 
 [[package]]
 name = "async-executor"
-version = "1.6.0"
+version = "1.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b0c4a4f319e45986f347ee47fef8bf5e81c9abc3f6f58dc2391439f30df65f0"
+checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c"
 dependencies = [
- "async-lock",
+ "async-lock 3.1.2",
  "async-task",
  "concurrent-queue",
  "fastrand 2.0.1",
- "futures-lite",
+ "futures-lite 2.0.1",
  "slab",
 ]
 
 [[package]]
 name = "async-global-executor"
-version = "2.3.1"
+version = "2.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776"
+checksum = "9b4353121d5644cdf2beb5726ab752e79a8db1ebb52031770ec47db31d245526"
 dependencies = [
  "async-channel",
  "async-executor",
- "async-io",
- "async-lock",
+ "async-io 2.2.1",
+ "async-lock 3.1.2",
  "blocking",
- "futures-lite",
+ "futures-lite 2.0.1",
  "once_cell",
 ]
 
@@ -399,27 +402,57 @@ version = "1.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af"
 dependencies = [
- "async-lock",
+ "async-lock 2.8.0",
  "autocfg",
  "cfg-if",
  "concurrent-queue",
- "futures-lite",
+ "futures-lite 1.13.0",
  "log",
  "parking",
- "polling",
+ "polling 2.8.0",
  "rustix 0.37.27",
  "slab",
- "socket2 0.4.9",
+ "socket2 0.4.10",
  "waker-fn",
 ]
 
+[[package]]
+name = "async-io"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6d3b15875ba253d1110c740755e246537483f152fa334f91abd7fe84c88b3ff"
+dependencies = [
+ "async-lock 3.1.2",
+ "cfg-if",
+ "concurrent-queue",
+ "futures-io",
+ "futures-lite 2.0.1",
+ "parking",
+ "polling 3.3.1",
+ "rustix 0.38.25",
+ "slab",
+ "tracing",
+ "windows-sys 0.52.0",
+]
+
 [[package]]
 name = "async-lock"
 version = "2.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b"
 dependencies = [
- "event-listener",
+ "event-listener 2.5.3",
+]
+
+[[package]]
+name = "async-lock"
+version = "3.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dea8b3453dd7cc96711834b75400d671b73e3656975fa68d9f277163b7f7e316"
+dependencies = [
+ "event-listener 4.0.0",
+ "event-listener-strategy",
+ "pin-project-lite",
 ]
 
 [[package]]
@@ -428,7 +461,7 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7a6012d170ad00de56c9ee354aef2e358359deb1ec504254e0e5a3774771de0e"
 dependencies = [
- "async-io",
+ "async-io 1.13.0",
  "async-trait",
  "futures-core",
  "reactor-trait",
@@ -448,7 +481,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.38",
+ "syn 2.0.39",
 ]
 
 [[package]]
@@ -495,9 +528,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
 
 [[package]]
 name = "base64"
-version = "0.21.4"
+version = "0.21.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2"
+checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
 
 [[package]]
 name = "bitflags"
@@ -531,16 +564,16 @@ dependencies = [
 
 [[package]]
 name = "blocking"
-version = "1.4.1"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8c36a4d0d48574b3dd360b4b7d95cc651d2b6557b6402848a27d4b228a473e2a"
+checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118"
 dependencies = [
  "async-channel",
- "async-lock",
+ "async-lock 3.1.2",
  "async-task",
  "fastrand 2.0.1",
  "futures-io",
- "futures-lite",
+ "futures-lite 2.0.1",
  "piper",
  "tracing",
 ]
@@ -558,9 +591,9 @@ dependencies = [
 
 [[package]]
 name = "brotli-decompressor"
-version = "2.5.0"
+version = "2.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da74e2b81409b1b743f8f0c62cc6254afefb8b8e50bbfe3735550f7aeefa3448"
+checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f"
 dependencies = [
  "alloc-no-stdlib",
  "alloc-stdlib",
@@ -586,9 +619,9 @@ checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
 
 [[package]]
 name = "bytestring"
-version = "1.3.0"
+version = "1.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "238e4886760d98c4f899360c834fa93e62cf7f721ac3c2da375cbdf4b8679aae"
+checksum = "74d80203ea6b29df88012294f62733de21cfeab47f17b41af3a38bc30a03ee72"
 dependencies = [
  "bytes",
 ]
@@ -631,7 +664,7 @@ dependencies = [
  "serde",
  "time 0.1.45",
  "wasm-bindgen",
- "windows-targets",
+ "windows-targets 0.48.5",
 ]
 
 [[package]]
@@ -655,9 +688,9 @@ dependencies = [
 
 [[package]]
 name = "config"
-version = "0.13.3"
+version = "0.13.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d379af7f68bfc21714c6c7dea883544201741d2ce8274bb12fa54f89507f52a7"
+checksum = "23738e11972c7643e4ec947840fc463b6a571afcd3e735bdfce7d03c7a784aca"
 dependencies = [
  "async-trait",
  "json5",
@@ -713,9 +746,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
 
 [[package]]
 name = "cpufeatures"
-version = "0.2.9"
+version = "0.2.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1"
+checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0"
 dependencies = [
  "libc",
 ]
@@ -731,9 +764,9 @@ dependencies = [
 
 [[package]]
 name = "crc-catalog"
-version = "2.2.0"
+version = "2.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484"
+checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
 
 [[package]]
 name = "crc32fast"
@@ -945,12 +978,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
 
 [[package]]
 name = "errno"
-version = "0.3.5"
+version = "0.3.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860"
+checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8"
 dependencies = [
  "libc",
- "windows-sys",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -959,6 +992,27 @@ version = "2.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
 
+[[package]]
+name = "event-listener"
+version = "4.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "770d968249b5d99410d61f5bf89057f3199a077a04d087092f58e7d10692baae"
+dependencies = [
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "event-listener-strategy"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3"
+dependencies = [
+ "event-listener 4.0.0",
+ "pin-project-lite",
+]
+
 [[package]]
 name = "executor-trait"
 version = "2.1.0"
@@ -1011,6 +1065,15 @@ dependencies = [
  "spin 0.9.8",
 ]
 
+[[package]]
+name = "flume"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181"
+dependencies = [
+ "spin 0.9.8",
+]
+
 [[package]]
 name = "fnv"
 version = "1.0.7"
@@ -1034,9 +1097,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
 
 [[package]]
 name = "form_urlencoded"
-version = "1.2.0"
+version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
 dependencies = [
  "percent-encoding",
 ]
@@ -1115,6 +1178,20 @@ dependencies = [
  "waker-fn",
 ]
 
+[[package]]
+name = "futures-lite"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3831c2651acb5177cbd83943f3d9c8912c5ad03c76afcc0e9511ba568ec5ebb"
+dependencies = [
+ "fastrand 2.0.1",
+ "futures-core",
+ "futures-io",
+ "memchr",
+ "parking",
+ "pin-project-lite",
+]
+
 [[package]]
 name = "futures-macro"
 version = "0.3.29"
@@ -1123,7 +1200,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.38",
+ "syn 2.0.39",
 ]
 
 [[package]]
@@ -1178,9 +1255,9 @@ dependencies = [
 
 [[package]]
 name = "getrandom"
-version = "0.2.10"
+version = "0.2.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
+checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
 dependencies = [
  "cfg-if",
  "libc",
@@ -1189,9 +1266,9 @@ dependencies = [
 
 [[package]]
 name = "gimli"
-version = "0.28.0"
+version = "0.28.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
+checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
 
 [[package]]
 name = "glob"
@@ -1201,9 +1278,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
 
 [[package]]
 name = "h2"
-version = "0.3.21"
+version = "0.3.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833"
+checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178"
 dependencies = [
  "bytes",
  "fnv",
@@ -1211,7 +1288,7 @@ dependencies = [
  "futures-sink",
  "futures-util",
  "http",
- "indexmap 1.9.3",
+ "indexmap 2.1.0",
  "slab",
  "tokio",
  "tokio-util",
@@ -1224,16 +1301,16 @@ version = "0.12.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
 dependencies = [
- "ahash 0.7.6",
+ "ahash 0.7.7",
 ]
 
 [[package]]
 name = "hashbrown"
-version = "0.14.1"
+version = "0.14.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12"
+checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156"
 dependencies = [
- "ahash 0.8.3",
+ "ahash 0.8.6",
  "allocator-api2",
 ]
 
@@ -1243,7 +1320,7 @@ version = "0.8.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
 dependencies = [
- "hashbrown 0.14.1",
+ "hashbrown 0.14.2",
 ]
 
 [[package]]
@@ -1287,9 +1364,9 @@ dependencies = [
 
 [[package]]
 name = "http"
-version = "0.2.9"
+version = "0.2.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
+checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb"
 dependencies = [
  "bytes",
  "fnv",
@@ -1336,7 +1413,7 @@ dependencies = [
  "httpdate",
  "itoa",
  "pin-project-lite",
- "socket2 0.4.9",
+ "socket2 0.4.10",
  "tokio",
  "tower-service",
  "tracing",
@@ -1387,9 +1464,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
 
 [[package]]
 name = "idna"
-version = "0.4.0"
+version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
+checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
 dependencies = [
  "unicode-bidi",
  "unicode-normalization",
@@ -1413,7 +1490,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
 dependencies = [
  "equivalent",
- "hashbrown 0.14.1",
+ "hashbrown 0.14.2",
  "serde",
 ]
 
@@ -1444,14 +1521,14 @@ checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
 dependencies = [
  "hermit-abi",
  "libc",
- "windows-sys",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
 name = "ipnet"
-version = "2.8.0"
+version = "2.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6"
+checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
 
 [[package]]
 name = "itertools"
@@ -1488,9 +1565,9 @@ dependencies = [
 
 [[package]]
 name = "js-sys"
-version = "0.3.64"
+version = "0.3.65"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a"
+checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8"
 dependencies = [
  "wasm-bindgen",
 ]
@@ -1523,7 +1600,7 @@ dependencies = [
  "async-reactor-trait",
  "async-trait",
  "executor-trait",
- "flume",
+ "flume 0.10.14",
  "futures-core",
  "futures-io",
  "parking_lot 0.12.1",
@@ -1543,9 +1620,20 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 
 [[package]]
 name = "libc"
-version = "0.2.149"
+version = "0.2.150"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
+checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
+
+[[package]]
+name = "libredox"
+version = "0.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8"
+dependencies = [
+ "bitflags 2.4.1",
+ "libc",
+ "redox_syscall 0.4.1",
+]
 
 [[package]]
 name = "linked-hash-map"
@@ -1561,15 +1649,15 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
 
 [[package]]
 name = "linux-raw-sys"
-version = "0.4.10"
+version = "0.4.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f"
+checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
 
 [[package]]
 name = "local-channel"
-version = "0.1.4"
+version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0a493488de5f18c8ffcba89eebb8532ffc562dc400490eb65b84893fae0b178"
+checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8"
 dependencies = [
  "futures-core",
  "futures-sink",
@@ -1578,9 +1666,9 @@ dependencies = [
 
 [[package]]
 name = "local-waker"
-version = "0.1.3"
+version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1"
+checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487"
 
 [[package]]
 name = "lock_api"
@@ -1646,16 +1734,22 @@ dependencies = [
 
 [[package]]
 name = "mio"
-version = "0.8.8"
+version = "0.8.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
+checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0"
 dependencies = [
  "libc",
  "log",
  "wasi 0.11.0+wasi-snapshot-preview1",
- "windows-sys",
+ "windows-sys 0.48.0",
 ]
 
+[[package]]
+name = "mutually_exclusive_features"
+version = "0.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d02c0b00610773bb7fc61d85e13d86c7858cbdf00e1a120bfc41bc055dbaa0e"
+
 [[package]]
 name = "native-tls"
 version = "0.2.11"
@@ -1730,9 +1824,9 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
 
 [[package]]
 name = "openssl"
-version = "0.10.57"
+version = "0.10.60"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c"
+checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800"
 dependencies = [
  "bitflags 2.4.1",
  "cfg-if",
@@ -1751,7 +1845,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.38",
+ "syn 2.0.39",
 ]
 
 [[package]]
@@ -1762,9 +1856,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
 
 [[package]]
 name = "openssl-sys"
-version = "0.9.93"
+version = "0.9.96"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d"
+checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f"
 dependencies = [
  "cc",
  "libc",
@@ -1856,7 +1950,7 @@ dependencies = [
  "libc",
  "redox_syscall 0.4.1",
  "smallvec",
- "windows-targets",
+ "windows-targets 0.48.5",
 ]
 
 [[package]]
@@ -1873,15 +1967,15 @@ checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
 
 [[package]]
 name = "percent-encoding"
-version = "2.3.0"
+version = "2.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
 
 [[package]]
 name = "pest"
-version = "2.7.4"
+version = "2.7.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c022f1e7b65d6a24c0dbbd5fb344c66881bc01f3e5ae74a1c8100f2f985d98a4"
+checksum = "ae9cee2a55a544be8b89dc6848072af97a20f2422603c10865be2a42b580fff5"
 dependencies = [
  "memchr",
  "thiserror",
@@ -1890,9 +1984,9 @@ dependencies = [
 
 [[package]]
 name = "pest_derive"
-version = "2.7.4"
+version = "2.7.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "35513f630d46400a977c4cb58f78e1bfbe01434316e60c37d27b9ad6139c66d8"
+checksum = "81d78524685f5ef2a3b3bd1cafbc9fcabb036253d9b1463e726a91cd16e2dfc2"
 dependencies = [
  "pest",
  "pest_generator",
@@ -1900,22 +1994,22 @@ dependencies = [
 
 [[package]]
 name = "pest_generator"
-version = "2.7.4"
+version = "2.7.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc9fc1b9e7057baba189b5c626e2d6f40681ae5b6eb064dc7c7834101ec8123a"
+checksum = "68bd1206e71118b5356dae5ddc61c8b11e28b09ef6a31acbd15ea48a28e0c227"
 dependencies = [
  "pest",
  "pest_meta",
  "proc-macro2",
  "quote",
- "syn 2.0.38",
+ "syn 2.0.39",
 ]
 
 [[package]]
 name = "pest_meta"
-version = "2.7.4"
+version = "2.7.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1df74e9e7ec4053ceb980e7c0c8bd3594e977fde1af91daba9c928e8e8c6708d"
+checksum = "7c747191d4ad9e4a4ab9c8798f1e82a39affe7ef9648390b7e5548d18e099de6"
 dependencies = [
  "once_cell",
  "pest",
@@ -1939,7 +2033,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.38",
+ "syn 2.0.39",
 ]
 
 [[package]]
@@ -1956,12 +2050,12 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
 
 [[package]]
 name = "pinky-swear"
-version = "6.1.0"
+version = "6.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d894b67aa7a4bf295db5e85349078c604edaa6fa5c8721e8eca3c7729a27f2ac"
+checksum = "6cfae3ead413ca051a681152bd266438d3bfa301c9bdf836939a14c721bb2a21"
 dependencies = [
  "doc-comment",
- "flume",
+ "flume 0.11.0",
  "parking_lot 0.12.1",
  "tracing",
 ]
@@ -1996,7 +2090,21 @@ dependencies = [
  "libc",
  "log",
  "pin-project-lite",
- "windows-sys",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "polling"
+version = "3.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf63fa624ab313c11656b4cda960bfc46c410187ad493c41f6ba2d8c1e991c9e"
+dependencies = [
+ "cfg-if",
+ "concurrent-queue",
+ "pin-project-lite",
+ "rustix 0.38.25",
+ "tracing",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
@@ -2037,9 +2145,9 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.69"
+version = "1.0.70"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
+checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
 dependencies = [
  "unicode-ident",
 ]
@@ -2112,15 +2220,6 @@ dependencies = [
  "bitflags 1.3.2",
 ]
 
-[[package]]
-name = "redox_syscall"
-version = "0.3.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
-dependencies = [
- "bitflags 1.3.2",
-]
-
 [[package]]
 name = "redox_syscall"
 version = "0.4.1"
@@ -2132,12 +2231,12 @@ dependencies = [
 
 [[package]]
 name = "redox_users"
-version = "0.4.3"
+version = "0.4.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
+checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4"
 dependencies = [
  "getrandom",
- "redox_syscall 0.2.16",
+ "libredox",
  "thiserror",
 ]
 
@@ -2191,7 +2290,7 @@ version = "0.11.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b"
 dependencies = [
- "base64 0.21.4",
+ "base64 0.21.5",
  "bytes",
  "encoding_rs",
  "futures-core",
@@ -2240,16 +2339,16 @@ dependencies = [
 
 [[package]]
 name = "ring"
-version = "0.17.4"
+version = "0.17.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fce3045ffa7c981a6ee93f640b538952e155f1ae3a1a02b84547fc7a56b7059a"
+checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b"
 dependencies = [
  "cc",
  "getrandom",
  "libc",
  "spin 0.9.8",
  "untrusted 0.9.0",
- "windows-sys",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -2299,20 +2398,20 @@ dependencies = [
  "io-lifetimes",
  "libc",
  "linux-raw-sys 0.3.8",
- "windows-sys",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
 name = "rustix"
-version = "0.38.19"
+version = "0.38.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "745ecfa778e66b2b63c88a61cb36e0eea109e803b0b86bf9879fbc77c70e86ed"
+checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e"
 dependencies = [
  "bitflags 2.4.1",
  "errno",
  "libc",
- "linux-raw-sys 0.4.10",
- "windows-sys",
+ "linux-raw-sys 0.4.11",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -2329,24 +2428,24 @@ dependencies = [
 
 [[package]]
 name = "rustls"
-version = "0.21.8"
+version = "0.21.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c"
+checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9"
 dependencies = [
  "log",
- "ring 0.17.4",
+ "ring 0.17.5",
  "rustls-webpki",
  "sct",
 ]
 
 [[package]]
 name = "rustls-connector"
-version = "0.18.3"
+version = "0.18.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "060bcc1795b840d0e56d78f3293be5f652aa1611d249b0e63ffe19f4a8c9ae23"
+checksum = "25da151615461c7347114b1ad1a7458b4cdebc69cb220cd140cd5cb324b1dd37"
 dependencies = [
  "log",
- "rustls 0.21.8",
+ "rustls 0.21.9",
  "rustls-native-certs",
  "rustls-webpki",
 ]
@@ -2365,11 +2464,11 @@ dependencies = [
 
 [[package]]
 name = "rustls-pemfile"
-version = "1.0.3"
+version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2"
+checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
 dependencies = [
- "base64 0.21.4",
+ "base64 0.21.5",
 ]
 
 [[package]]
@@ -2378,7 +2477,7 @@ version = "0.101.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
 dependencies = [
- "ring 0.17.4",
+ "ring 0.17.5",
  "untrusted 0.9.0",
 ]
 
@@ -2394,7 +2493,7 @@ version = "0.1.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88"
 dependencies = [
- "windows-sys",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -2405,12 +2504,12 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
 
 [[package]]
 name = "sct"
-version = "0.7.0"
+version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
+checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
 dependencies = [
- "ring 0.16.20",
- "untrusted 0.7.1",
+ "ring 0.17.5",
+ "untrusted 0.9.0",
 ]
 
 [[package]]
@@ -2444,29 +2543,29 @@ checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
 
 [[package]]
 name = "serde"
-version = "1.0.189"
+version = "1.0.193"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537"
+checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.189"
+version = "1.0.193"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5"
+checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.38",
+ "syn 2.0.39",
 ]
 
 [[package]]
 name = "serde_json"
-version = "1.0.107"
+version = "1.0.108"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
+checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
 dependencies = [
  "itoa",
  "ryu",
@@ -2531,9 +2630,9 @@ dependencies = [
 
 [[package]]
 name = "serde_yaml"
-version = "0.9.25"
+version = "0.9.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574"
+checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c"
 dependencies = [
  "indexmap 2.1.0",
  "itoa",
@@ -2593,15 +2692,15 @@ dependencies = [
 
 [[package]]
 name = "smallvec"
-version = "1.11.1"
+version = "1.11.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a"
+checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
 
 [[package]]
 name = "socket2"
-version = "0.4.9"
+version = "0.4.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
+checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
 dependencies = [
  "libc",
  "winapi",
@@ -2609,12 +2708,12 @@ dependencies = [
 
 [[package]]
 name = "socket2"
-version = "0.5.4"
+version = "0.5.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e"
+checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
 dependencies = [
  "libc",
- "windows-sys",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -2659,7 +2758,7 @@ version = "0.6.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fa8241483a83a3f33aa5fff7e7d9def398ff9990b2752b6c6112b83c6d246029"
 dependencies = [
- "ahash 0.7.6",
+ "ahash 0.7.7",
  "atoi",
  "base64 0.13.1",
  "bitflags 1.3.2",
@@ -2671,7 +2770,7 @@ dependencies = [
  "dirs",
  "dotenvy",
  "either",
- "event-listener",
+ "event-listener 2.5.3",
  "futures-channel",
  "futures-core",
  "futures-intrusive",
@@ -2753,7 +2852,7 @@ dependencies = [
  "config",
  "derive_builder",
  "futures",
- "futures-lite",
+ "futures-lite 1.13.0",
  "futures-util",
  "glob",
  "hmac",
@@ -2775,7 +2874,7 @@ dependencies = [
  "tracing",
  "tracing-actix-web",
  "tracing-bunyan-formatter",
- "tracing-log",
+ "tracing-log 0.1.4",
  "tracing-subscriber",
  "uuid",
 ]
@@ -2816,9 +2915,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.38"
+version = "2.0.39"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
+checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2860,35 +2959,35 @@ dependencies = [
 
 [[package]]
 name = "tempfile"
-version = "3.8.0"
+version = "3.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
+checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
 dependencies = [
  "cfg-if",
  "fastrand 2.0.1",
- "redox_syscall 0.3.5",
- "rustix 0.38.19",
- "windows-sys",
+ "redox_syscall 0.4.1",
+ "rustix 0.38.25",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
 name = "thiserror"
-version = "1.0.49"
+version = "1.0.50"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4"
+checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.49"
+version = "1.0.50"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc"
+checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.38",
+ "syn 2.0.39",
 ]
 
 [[package]]
@@ -2958,9 +3057,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
 
 [[package]]
 name = "tokio"
-version = "1.33.0"
+version = "1.34.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653"
+checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9"
 dependencies = [
  "backtrace",
  "bytes",
@@ -2970,20 +3069,20 @@ dependencies = [
  "parking_lot 0.12.1",
  "pin-project-lite",
  "signal-hook-registry",
- "socket2 0.5.4",
+ "socket2 0.5.5",
  "tokio-macros",
- "windows-sys",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
 name = "tokio-macros"
-version = "2.1.0"
+version = "2.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
+checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.38",
+ "syn 2.0.39",
 ]
 
 [[package]]
@@ -3020,9 +3119,9 @@ dependencies = [
 
 [[package]]
 name = "tokio-util"
-version = "0.7.9"
+version = "0.7.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d"
+checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
 dependencies = [
  "bytes",
  "futures-core",
@@ -3049,9 +3148,9 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
 
 [[package]]
 name = "tracing"
-version = "0.1.39"
+version = "0.1.40"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee2ef2af84856a50c1d430afce2fdded0a4ec7eda868db86409b4543df0797f9"
+checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
 dependencies = [
  "log",
  "pin-project-lite",
@@ -3061,11 +3160,12 @@ dependencies = [
 
 [[package]]
 name = "tracing-actix-web"
-version = "0.7.7"
+version = "0.7.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94982c2ad939d5d0bfd71c2f9b7ed273c72348485c72bb87bb4db6bd69df10cb"
+checksum = "1fe0d5feac3f4ca21ba33496bcb1ccab58cca6412b1405ae80f0581541e0ca78"
 dependencies = [
  "actix-web",
+ "mutually_exclusive_features",
  "pin-project",
  "tracing",
  "uuid",
@@ -3079,7 +3179,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.38",
+ "syn 2.0.39",
 ]
 
 [[package]]
@@ -3088,7 +3188,7 @@ version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b5c266b9ac83dedf0e0385ad78514949e6d89491269e7065bee51d2bb8ec7373"
 dependencies = [
- "ahash 0.8.3",
+ "ahash 0.8.6",
  "gethostname",
  "log",
  "serde",
@@ -3096,7 +3196,7 @@ dependencies = [
  "time 0.3.30",
  "tracing",
  "tracing-core",
- "tracing-log",
+ "tracing-log 0.1.4",
  "tracing-subscriber",
 ]
 
@@ -3112,20 +3212,31 @@ dependencies = [
 
 [[package]]
 name = "tracing-log"
-version = "0.1.3"
+version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
+checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2"
 dependencies = [
- "lazy_static",
  "log",
+ "once_cell",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
+dependencies = [
+ "log",
+ "once_cell",
  "tracing-core",
 ]
 
 [[package]]
 name = "tracing-subscriber"
-version = "0.3.17"
+version = "0.3.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77"
+checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
 dependencies = [
  "matchers",
  "nu-ansi-term",
@@ -3136,7 +3247,7 @@ dependencies = [
  "thread_local",
  "tracing",
  "tracing-core",
- "tracing-log",
+ "tracing-log 0.2.0",
 ]
 
 [[package]]
@@ -3210,9 +3321,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
 
 [[package]]
 name = "url"
-version = "2.4.1"
+version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
+checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
 dependencies = [
  "form_urlencoded",
  "idna",
@@ -3221,9 +3332,9 @@ dependencies = [
 
 [[package]]
 name = "uuid"
-version = "1.5.0"
+version = "1.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc"
+checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560"
 dependencies = [
  "getrandom",
  "serde",
@@ -3276,9 +3387,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.87"
+version = "0.2.88"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342"
+checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce"
 dependencies = [
  "cfg-if",
  "wasm-bindgen-macro",
@@ -3286,24 +3397,24 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.87"
+version = "0.2.88"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd"
+checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217"
 dependencies = [
  "bumpalo",
  "log",
  "once_cell",
  "proc-macro2",
  "quote",
- "syn 2.0.38",
+ "syn 2.0.39",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-futures"
-version = "0.4.37"
+version = "0.4.38"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03"
+checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02"
 dependencies = [
  "cfg-if",
  "js-sys",
@@ -3313,9 +3424,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.87"
+version = "0.2.88"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d"
+checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -3323,28 +3434,28 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.87"
+version = "0.2.88"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
+checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.38",
+ "syn 2.0.39",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.87"
+version = "0.2.88"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
+checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b"
 
 [[package]]
 name = "web-sys"
-version = "0.3.64"
+version = "0.3.65"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b"
+checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
@@ -3356,7 +3467,7 @@ version = "0.22.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53"
 dependencies = [
- "ring 0.17.4",
+ "ring 0.17.5",
  "untrusted 0.9.0",
 ]
 
@@ -3407,7 +3518,7 @@ version = "0.51.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
 dependencies = [
- "windows-targets",
+ "windows-targets 0.48.5",
 ]
 
 [[package]]
@@ -3416,7 +3527,16 @@ version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
 dependencies = [
- "windows-targets",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.0",
 ]
 
 [[package]]
@@ -3425,13 +3545,28 @@ version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
 dependencies = [
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.0",
+ "windows_aarch64_msvc 0.52.0",
+ "windows_i686_gnu 0.52.0",
+ "windows_i686_msvc 0.52.0",
+ "windows_x86_64_gnu 0.52.0",
+ "windows_x86_64_gnullvm 0.52.0",
+ "windows_x86_64_msvc 0.52.0",
 ]
 
 [[package]]
@@ -3440,42 +3575,84 @@ version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
 
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
+
 [[package]]
 name = "windows_aarch64_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
 
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
+
 [[package]]
 name = "windows_i686_gnu"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
 
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
+
 [[package]]
 name = "windows_i686_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
 
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
+
 [[package]]
 name = "windows_x86_64_gnu"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
 
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
+
 [[package]]
 name = "windows_x86_64_gnullvm"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
 
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
+
 [[package]]
 name = "windows_x86_64_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
 
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
+
 [[package]]
 name = "winreg"
 version = "0.50.0"
@@ -3483,7 +3660,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
 dependencies = [
  "cfg-if",
- "windows-sys",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -3501,6 +3678,26 @@ version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
 
+[[package]]
+name = "zerocopy"
+version = "0.7.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e97e415490559a91254a2979b4829267a57d2fcd741a98eee8b722fb57289aa0"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd7e48ccf166952882ca8bd778a43502c64f33bf94c12ebe2a7f08e5a0f6689f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+]
+
 [[package]]
 name = "zstd"
 version = "0.12.4"
diff --git a/src/forms/mod.rs b/src/forms/mod.rs
index 957c91d8..ae65e1c4 100644
--- a/src/forms/mod.rs
+++ b/src/forms/mod.rs
@@ -1,6 +1,6 @@
 mod rating;
 
-pub(crate) mod stack;
+pub mod stack;
 pub mod user;
 
 pub use rating::*;
diff --git a/src/forms/stack.rs b/src/forms/stack.rs
index c3f108a1..91b34cbc 100644
--- a/src/forms/stack.rs
+++ b/src/forms/stack.rs
@@ -1,3 +1,4 @@
+use std::collections::HashMap;
 use serde::{Deserialize, Serialize};
 use serde_json::Value;
 use serde_valid::Validate;
@@ -11,8 +12,10 @@ pub struct Role {
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Requirements {
-    #[validate(minimum=0.1)]
-    pub cpu: Option<f64>,
+    #[validate(min_length=1)]
+    #[validate(max_length=10)]
+    #[validate(pattern = r"^\d+\.?[0-9]+$")]
+    pub cpu: Option<String>,
     #[validate(min_length=1)]
     #[validate(max_length=10)]
     #[validate(pattern = r"^\d+G$")]
@@ -25,12 +28,18 @@ pub struct Requirements {
     pub ram_size: Option<String>,
 }
 
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct Port {
+    pub host_port: Option<String>,
+    pub container_port: Option<String>
+}
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct Ports {
     #[serde(rename(deserialize = "sharedPorts"))]
     #[serde(rename(serialize = "shared_ports"))]
-    #[serde(alias = "shared_ports")]
-    pub shared_ports: Option<Vec<String>>,
+    // #[serde(alias = "shared_ports")]
+    pub shared_ports: Option<Vec<Port>>,
     pub ports: Option<Vec<String>>,
 }
 
@@ -159,42 +168,32 @@ pub struct Var {
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
 pub struct Price {
     pub value: f64
 }
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-#[serde(rename_all = "camelCase")]
 pub struct Custom {
     pub web: Vec<Web>,
     pub feature: Option<Vec<Feature>>,
     pub service: Option<Vec<Service>>,
-    #[serde(rename = "servers_count")]
     #[validate(minimum = 0)]
     #[validate(maximum = 10)]
     pub servers_count: u32,
     #[validate(min_length=3)]
     #[validate(max_length=50)]
-    #[serde(rename = "custom_stack_code")]
     pub custom_stack_code: String,
-    #[serde(rename = "project_git_url")]
     #[validate(min_length=3)]
     #[validate(max_length=255)]
     pub project_git_url: Option<String>,
-    #[serde(rename = "custom_stack_category")]
     pub custom_stack_category: Option<Vec<String>>,
-    #[serde(rename = "custom_stack_short_description")]
     pub custom_stack_short_description: Option<String>,
-    #[serde(rename = "custom_stack_description")]
     pub custom_stack_description: Option<String>,
-    #[serde(rename = "project_name")]
     #[validate(min_length=3)]
     #[validate(max_length=255)]
     pub project_name: String,
-    #[serde(rename = "project_overview")]
     pub project_overview: Option<String>,
-    #[serde(rename = "project_description")]
     pub project_description: Option<String>,
+    pub networks: Option<Vec<String>>, // all networks
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
@@ -222,8 +221,6 @@ pub struct App {
     #[serde(flatten)]
     pub role: Role,
     pub default: Option<bool>,
-    #[serde(flatten)]
-    pub ports: Option<Ports>,
     pub versions: Option<Vec<Version>>,
     #[serde(flatten)]
     pub docker_image: DockerImage,
@@ -242,6 +239,7 @@ pub struct App {
     pub domain: Option<String>,
     pub category_id: Option<u32>,
     pub parent_app_id: Option<u32>,
+    #[serde(skip_serializing_if = "Option::is_none")]
     pub descr: Option<String>,
     pub full_description: Option<String>,
     pub description: Option<String>,
@@ -250,9 +248,42 @@ pub struct App {
     pub repo_dir: Option<String>,
     pub url_app: Option<String>,
     pub url_git: Option<String>,
+    pub restart: Option<String>,
+    pub volumes: Option<Vec<Volume>>,
+    #[serde(flatten)]
+    pub environment: Environment,
+    #[serde(flatten)]
+    pub network: Networks,
+    // #[serde(flatten)]
+    // pub ports: Ports,
+    #[serde(rename(deserialize = "sharedPorts"))]
+    #[serde(rename(serialize = "shared_ports"))]
+    // #[serde(alias = "shared_ports")]
+    pub ports: Option<Vec<Port>>,
 }
 
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct Environment {
+    environment: Option<Vec<HashMap<String, String>>>
+}
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct Volume {
+    host_path: Option<String>,
+    container_path: Option<String>
+}
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct Volumes {
+    volumes: Vec<Volume>
+}
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct Networks {
+    //network: Option<Vec<String>>
+    network: Option<String>
+}
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct Web {
     #[serde(flatten)]
     pub app: App,
@@ -260,28 +291,33 @@ pub struct Web {
     pub main: bool,
 }
 
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct Feature {
+    // #[serde(rename(deserialize = "sharedPorts"))]
+    // #[serde(rename(serialize = "shared_ports"))]
+    // #[serde(alias = "shared_ports")]
+    // pub shared_ports: Option<Vec<Port>>,
     #[serde(flatten)]
     pub app: App,
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-#[serde(rename_all = "camelCase")]
 pub struct Service {
+    // #[serde(rename(deserialize = "sharedPorts"))]
+    // #[serde(rename(serialize = "shared_ports"))]
+    // #[serde(alias = "shared_ports")]
+    // pub shared_ports: Option<Vec<Port>>,
     #[serde(flatten)]
     pub(crate) app: App,
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
 pub struct Icon {
     pub light: IconLight,
     pub dark: IconDark,
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
 pub struct IconLight {
     pub width: i64,
     pub height: i64,
@@ -289,7 +325,6 @@ pub struct IconLight {
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
 pub struct IconDark {
 }
 
diff --git a/src/helpers/json.rs b/src/helpers/json.rs
index 87e73273..6a08405b 100644
--- a/src/helpers/json.rs
+++ b/src/helpers/json.rs
@@ -133,62 +133,4 @@ where
             list,
         }
     }
-
-    // pub(crate) fn ok(id: i32, message: &str) -> JsonResponse<T> {
-    //
-    //     let msg = if !message.trim().is_empty() {
-    //         message.to_string()
-    //     }
-    //     else{
-    //         String::from("Success")
-    //     };
-    //
-    //     JsonResponse {
-    //         message: msg,
-    //         id: Some(id),
-    //         item: None,
-    //         list: None,
-    //     }
-    // }
-
-    // pub(crate) fn not_found() -> Self {
-    //     JsonResponse {
-    //         id: None,
-    //         item: None,
-    //         message: format!("Object not found"),
-    //         list: None,
-    //     }
-    // }
-    //
-    // pub(crate) fn internal_error(message: &str) -> Self {
-    //
-    //     let msg = if !message.trim().is_empty() {
-    //         message.to_string()
-    //     }
-    //     else{
-    //         String::from("Internal error")
-    //     };
-    //     JsonResponse {
-    //         id: None,
-    //         item: None,
-    //         message: msg,
-    //         list: None,
-    //     }
-    // }
-    //
-    // pub(crate) fn not_valid(message: &str) -> Self {
-    //
-    //     let msg = if !message.trim().is_empty() {
-    //         message.to_string()
-    //     }
-    //     else{
-    //         String::from("Validation error")
-    //     };
-    //     JsonResponse {
-    //         id: None,
-    //         item: None,
-    //         message: msg,
-    //         list: None,
-    //     }
-    // }
 }
diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index c319d8a9..316a03fe 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -18,6 +18,18 @@ impl Default for Config {
     }
 }
 
+impl Default for Port{
+    fn default() -> Self {
+        Port {
+            target: 80,
+            host_ip: None,
+            published: None,
+            protocol: None,
+            mode: None,
+        }
+    }
+}
+
 /// A builder for constructing docker compose.
 #[derive(Clone, Debug)]
 pub struct DcBuilder {
@@ -25,26 +37,55 @@ pub struct DcBuilder {
     pub(crate) stack: Stack
 }
 
+// impl TryInto<Vec<Port>> for stack::Ports {
+//     type Error = String;
+//     fn try_into(self) -> Result<Vec<Port>, Self::Error> {
+//         convert_shared_ports(self.shared_ports.clone())
+//     }
+// }
+
+
 impl TryInto<Vec<Port>> for stack::Ports {
     type Error = String;
     fn try_into(self) -> Result<Vec<Port>, Self::Error> {
-        convert_shared_ports(self.shared_ports.clone().unwrap())
+        convert_shared_ports(self.shared_ports.clone())
     }
 }
 
 
-fn convert_shared_ports(ports: Vec<String>) -> Result<Vec<Port>, String> {
+fn convert_shared_ports(ports: Option<Vec<stack::Port>>) -> Result<Vec<Port>, String> {
+    tracing::debug!("convert shared ports {:?}", &ports);
     let mut _ports: Vec<Port> = vec![];
-    for p in ports {
-        let port = p.parse::<u16>().map_err(|e| e.to_string())?;
-        _ports.push(Port {
-            target: port,
-            host_ip: None,
-            published: Some(PublishedPort::Single(port)),
-            protocol: None,
-            mode: None,
-        });
+    match ports {
+        Some(ports) => {
+            tracing::debug!("Ports >>>> {:?}", ports);
+            for port in ports {
+                let cp  = port.container_port
+                    .unwrap_or("".to_string())
+                    .parse::<u16>().map_err(|err| "Could not parse port".to_string() )?;
+                let hp = port.host_port
+                    .unwrap_or("".to_string())
+                    .parse::<u16>().map_err(|err| "Could not parse port".to_string() )?;
+
+                tracing::debug!("Port conversion result: cp: {:?} hp: {:?}", cp, hp);
+                _ports.push(
+                    Port {
+                        target: cp,
+                        host_ip: None,
+                        published: Some(PublishedPort::Single(hp)),
+                        protocol: None,
+                        mode: None,
+                    }
+                );
+            }
+        }
+        None => {
+            tracing::debug!("No ports defined by user");
+            return Ok(_ports);
+        }
     }
+
+    tracing::debug!("ports {:?}", _ports);
     Ok(_ports)
 }
 
@@ -59,109 +100,112 @@ impl DcBuilder {
 
 
     pub fn build(&self) -> Option<String> {
-
-        tracing::debug!("Start build docker compose from {:?}", &self.stack.body);
-        let _stack = serde_json::from_value::<StackForm>(self.stack.body.clone());
-        let mut services = indexmap::IndexMap::new();
-        match _stack  {
-            Ok(apps) => {
-                println!("stack item {:?}", apps.custom.web);
-
-                for app_type in apps.custom.web {
-                    let code = app_type.app.code.clone().to_owned();
-                    let mut service = Service {
-                        image: Some(app_type.app.docker_image.to_string()),
-                        ..Default::default()
-                    };
-
-                    if let Some(ports) = &app_type.app.ports {
-                        if !ports.shared_ports.clone()?.is_empty() {
-                            service.ports = Ports::Long(app_type.app.ports?.try_into().unwrap())
-                        }
-                    }
-
-                    service.restart = Some("always".to_owned());
-                    services.insert(
-                        code,
-                        Some(service),
-                    );
-                }
-
-                if let Some(srvs) = apps.custom.service {
-
-                    if !srvs.is_empty() {
-
-                        for app_type in srvs {
-                            let code = app_type.app.code.to_owned();
-                            let mut service = Service {
-                                image: Some(app_type.app.docker_image.to_string()),
-                                ..Default::default()
-                            };
-
-                            if let Some(ports) = &app_type.app.ports {
-                                if !ports.shared_ports.clone()?.is_empty() {
-                                    service.ports = Ports::Long(app_type.app.ports?.try_into().unwrap())
-                                }
-                            }
-                            service.restart = Some("always".to_owned());
-                            services.insert(
-                                code,
-                                Some(service),
-                            );
-                        }
-                    }
-                }
-                if let Some(features) = apps.custom.feature {
-
-                    if !features.is_empty() {
-
-                        for app_type in features {
-                            let code = app_type.app.code.to_owned();
-                            let mut service = Service {
-                                // image: Some(app.dockerhub_image.as_ref().unwrap().to_owned()),
-                                image: Some(app_type.app.docker_image.to_string()),
-                                ..Default::default()
-                            };
-
-                            if let Some(ports) = &app_type.app.ports {
-                                if !ports.shared_ports.clone()?.is_empty() {
-                                    service.ports = Ports::Long(app_type.app.ports?.try_into().unwrap())
-                                }
-                            }
-                            service.restart = Some("always".to_owned());
-                            services.insert(
-                                code,
-                                Some(service),
-                            );
-                        }
-                    }
-                }
-            }
-            Err(e) => {
-                tracing::debug!("Unpack stack form {:?}", e);
-                ()
-            }
-        }
-
-        let compose_content = Compose {
-            version: Some("3.8".to_string()),
-            services: {
-                Services(services)
-            },
-            ..Default::default()
-        };
-
-        let fname= format!("./files/{}.yml", self.stack.stack_id);
-        tracing::debug!("Save docker compose to file {:?}", fname);
-        let target_file = std::path::Path::new(fname.as_str());
-        // serialize to string
-        let serialized = match serde_yaml::to_string(&compose_content) {
-            Ok(s) => s,
-            Err(e) => panic!("Failed to serialize docker-compose file: {}", e),
-        };
-        // serialize to file
-        std::fs::write(target_file, serialized.clone()).unwrap();
-
-        Some(serialized)
+        //
+        // tracing::debug!("Start build docker compose from {:?}", &self.stack.body);
+        // let _stack = serde_json::from_value::<StackForm>(self.stack.body.clone());
+        // let mut services = indexmap::IndexMap::new();
+        // match _stack  {
+        //     Ok(apps) => {
+        //         // tracing::debug!("stack item {:?}", apps.custom.web);
+        //
+        //         for app_type in apps.custom.web {
+        //             let code = app_type.app.code.clone().to_owned();
+        //             let mut service = Service {
+        //                 image: Some(app_type.app.docker_image.to_string()),
+        //                 ..Default::default()
+        //             };
+        //
+        //             service.ports = Ports::Long(app_type.app.ports.unwrap().try_into().unwrap());
+        //             service.restart = Some("always".to_owned());
+        //             tracing::debug!("service 1 {:?}", &service);
+        //             services.insert(
+        //                 code,
+        //                 Some(service),
+        //             );
+        //         }
+        //
+        //
+        //         if let Some(srvs) = apps.custom.service {
+        //
+        //             if !srvs.is_empty() {
+        //
+        //                 for app_type in srvs {
+        //                     let code = app_type.app.code.to_owned();
+        //                     let mut service = Service {
+        //                         image: Some(app_type.app.docker_image.to_string()),
+        //                         ..Default::default()
+        //                     };
+        //
+        //                     // if let Some(ports) = &app_type.app._ports {
+        //                     //     tracing::debug!("service2 ports {:?}", ports);
+        //                     //     service.ports = Ports::Long(ports.try_into().unwrap())
+        //                     // }
+        //
+        //                     service.ports = Ports::Long(app_type.app.ports.unwrap().try_into().unwrap());
+        //                     service.restart = Some("always".to_owned());
+        //                     services.insert(
+        //                         code,
+        //                         Some(service),
+        //                     );
+        //                 }
+        //                 // tracing::debug!("services {:?}", services);
+        //             }
+        //         }
+        //
+        //         if let Some(features) = apps.custom.feature {
+        //
+        //             if !features.is_empty() {
+        //
+        //                 for app_type in features {
+        //                     let code = app_type.app.code.to_owned();
+        //                     let mut service = Service {
+        //                         // image: Some(app.dockerhub_image.as_ref().unwrap().to_owned()),
+        //                         image: Some(app_type.app.docker_image.to_string()),
+        //                         ..Default::default()
+        //                     };
+        //
+        //                     service.ports = Ports::Long(app_type.app.ports.unwrap().try_into().unwrap());
+        //                     // if let Some(ports) = &app_type.app._ports {
+        //                     //     tracing::debug!("service3 ports {:?}", ports);
+        //                     //     service.ports = Ports::Long(ports.try_into().unwrap())
+        //                     // }
+        //                     service.restart = Some("always".to_owned());
+        //                     services.insert(
+        //                         code,
+        //                         Some(service),
+        //                     );
+        //                 }
+        //                 // tracing::debug!("services### {:?}", &service);
+        //             }
+        //         }
+        //         tracing::debug!("services {:?}", &services);
+        //     }
+        //     Err(e) => {
+        //         tracing::debug!("Unpack stack form error {:?}", e);
+        //         ()
+        //     }
+        // }
+        //
+        // let compose_content = Compose {
+        //     version: Some("3.8".to_string()),
+        //     services: {
+        //         Services(services)
+        //     },
+        //     ..Default::default()
+        // };
+        //
+        // let fname= format!("./files/{}.yml", self.stack.stack_id);
+        // tracing::debug!("Saving docker compose to file {:?}", fname);
+        // let target_file = std::path::Path::new(fname.as_str());
+        // // serialize to string
+        // let serialized = match serde_yaml::to_string(&compose_content) {
+        //     Ok(s) => s,
+        //     Err(e) => panic!("Failed to serialize docker-compose file: {}", e),
+        // };
+        // // serialize to file
+        // std::fs::write(target_file, serialized.clone()).unwrap();
+        //
+        // Some(serialized)
+        None
     }
 }
diff --git a/src/middleware/trydirect.rs b/src/middleware/trydirect.rs
index 1d01c504..066c6784 100644
--- a/src/middleware/trydirect.rs
+++ b/src/middleware/trydirect.rs
@@ -11,7 +11,7 @@ use std::sync::Arc;
 
 use crate::models::user::User;
 
-#[tracing::instrument(name = "Trydirect bearer guard.")]
+#[tracing::instrument(name = "TryDirect bearer guard.")]
 pub async fn bearer_guard(
     req: ServiceRequest,
     credentials: BearerAuth,
diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs
index 1d648153..7c45ef55 100644
--- a/src/routes/stack/add.rs
+++ b/src/routes/stack/add.rs
@@ -38,6 +38,7 @@ pub async fn add(
     };
 
     let stack_name = form.custom.custom_stack_code.clone();
+    tracing::debug!("form before convert {:?}", form);
 
     let query_span = tracing::info_span!("Check project/stack existence by custom_stack_code.");
     match sqlx::query_as!(
@@ -81,12 +82,14 @@ pub async fn add(
 
     let query_span = tracing::info_span!("Saving new stack details into the database");
 
-    let errors = form.validate().is_ok();
-    println!("{:?}",errors);
+    let errors = form.validate().unwrap_err();
+    tracing::debug!("{:?}",errors);
+
+
     let body: Value = match serde_json::to_value::<StackForm>(form) {
         Ok(body) => body,
         Err(err) => {
-            tracing::error!("request_id {} unwrap body {:?}", request_id, err);
+            tracing::error!("Request_id {} error unwrap body {:?}", request_id, err);
             serde_json::to_value::<StackForm>(StackForm::default()).unwrap()
         }
     };
diff --git a/src/routes/stack/compose.rs b/src/routes/stack/compose.rs
index 4de94f84..58f82f76 100644
--- a/src/routes/stack/compose.rs
+++ b/src/routes/stack/compose.rs
@@ -11,7 +11,7 @@ use actix_web::{get, post};
 use sqlx::PgPool;
 use std::str;
 use tracing::Instrument;
-use uuid::Uuid;
+// use uuid::Uuid;
 use crate::helpers::stack::builder::DcBuilder;
 
 #[tracing::instrument(name = "User's generate docker-compose.")]
@@ -35,7 +35,7 @@ pub async fn add(
         .await
     {
         Ok(stack) => {
-            tracing::info!("stack found: {:?}", stack.id,);
+            tracing::info!("stack found: {:?}", stack.id);
             Some(stack)
         }
         Err(sqlx::Error::RowNotFound) => {
@@ -88,7 +88,7 @@ pub async fn admin(
         .await
     {
         Ok(stack) => {
-            tracing::info!("stack found: {:?}", stack.id,);
+            tracing::info!("stack found: {:?}", stack.id);
             Some(stack)
         }
         Err(sqlx::Error::RowNotFound) => {
@@ -105,11 +105,20 @@ pub async fn admin(
         Some(stack) => {
             let id = stack.id.clone();
             let dc = DcBuilder::new(stack);
-            let fc = dc.build();
+            let fc = match dc.build() {
+                Some(fc) => {
+                    fc
+                }
+                None => {
+                    tracing::error!("Error. Compose builder returned an empty string");
+                    "".to_string()
+                }
+
+            };
             // tracing::debug!("Docker compose file content {:?}", fc);
             return JsonResponse::build()
                 .set_id(id)
-                .set_item(fc.unwrap()).ok("Success".to_owned());
+                .set_item(fc).ok("Success".to_owned());
 
         }
         None => {
diff --git a/tests/app.json b/tests/app.json
new file mode 100644
index 00000000..cb4efb1a
--- /dev/null
+++ b/tests/app.json
@@ -0,0 +1,99 @@
+{
+  "_etag": null,
+  "_id": 180,
+  "_created": "2021-12-17T08:11:40.875486",
+  "_updated": "2023-11-21T17:37:24.594545",
+  "name": "Airflow",
+  "code": "airflow",
+  "role": [
+    "airflow"
+  ],
+  "type": "web",
+  "default": true,
+  "popularity": null,
+  "descr": null,
+  "ports": {
+    "private": [
+      "31"
+    ]
+  },
+  "commercial": null,
+  "subscription": null,
+  "autodeploy": null,
+  "suggested": null,
+  "dependency": null,
+  "avoid_render": null,
+  "price": null,
+  "icon": {
+    "light": {
+      "width": 150,
+      "height": 150,
+      "image": "7f41d873-2de5-4c6e-a037-42eeff572db6.svg"
+    },
+    "dark": {
+    }
+  },
+  "category_id": null,
+  "parent_app_id": null,
+  "full_description": null,
+  "description": "<p class=\"content-editor__unstyled\">Airflow description </p>",
+  "plan_type": null,
+  "ansible_var": null,
+  "repo_dir": null,
+  "cpu": "0.0",
+  "ram_size": null,
+  "disk_size": null,
+  "dockerhub_image": "airflow",
+  "form": null,
+  "category": [
+    null
+  ],
+  "group": [
+  ],
+  "versions": [
+    {
+      "_id": 425,
+      "name": "Airflow",
+      "version": "2.7.3",
+      "update_status": "published",
+      "tag": "latest"
+    },
+    {
+      "_id": 426,
+      "name": "Airflow",
+      "version": "2.7.1-unstable",
+      "update_status": "published",
+      "tag": "stable"
+    }
+  ],
+  "links": [
+  ],
+  "domain": "latestports.com",
+  "sharedPorts": [
+    {
+      "host_port": "5000",
+      "container_port": "5000"
+    }
+  ],
+  "main": true,
+  "version": {
+    "_id": 426,
+    "name": "Airflow",
+    "version": "2.7.1-unstable",
+    "update_status": "published",
+    "tag": "stable"
+  },
+  "environment": [
+    {
+      "ENV_VAR1": "ENV_VAR1_VALUE"
+    }
+  ],
+  "network": "testnetwork",
+  "restart": "always",
+  "volumes": [
+    {
+      "host_path": "/airflow/host/path/",
+      "container_path": "/airflow/container/path"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/model_user_stack.rs b/tests/model_user_stack.rs
new file mode 100644
index 00000000..be93cec0
--- /dev/null
+++ b/tests/model_user_stack.rs
@@ -0,0 +1,62 @@
+use stacker::forms::stack::StackForm;
+use stacker::forms::stack::App;
+use std::fs;
+use std::collections::HashMap;
+
+//  Unit Test
+
+#[test]
+fn test_deserialize_user_stack_web() {
+
+    let body_str = fs::read_to_string("./tests/web-item.json").unwrap();
+    // let form:serde_json::Value = serde_json::from_str(&body_str).unwrap();
+    let form:App = serde_json::from_str(&body_str).unwrap();
+    println!("{:?}", form);
+    // {
+    //     Ok(f) => {
+    //         f
+    //     }
+    //     Err(_err) => {
+    //         let msg = format!("Invalid data. {:?}", _err);
+    //         return JsonResponse::<StackForm>::build().err(msg);
+    //     }
+    // };
+    //
+    // assert_eq!(result, 12);
+}
+#[test]
+fn test_deserialize_user_stack() {
+
+    let body_str = fs::read_to_string("./tests/custom-stack-payload-11.json").unwrap();
+    let form = serde_json::from_str::<StackForm>(&body_str).unwrap();
+    println!("{:?}", form);
+    // @todo assert required data
+
+    // {
+    //     Ok(f) => {
+    //         f
+    //     }
+    //     Err(_err) => {
+    //         let msg = format!("Invalid data. {:?}", _err);
+    //         return JsonResponse::<StackForm>::build().err(msg);
+    //     }
+    // };
+    //
+    // assert_eq!(result, 12);
+
+    // let form:Environment = serde_json::from_str(&body_str).unwrap();
+
+    // let body_str = r#"
+    // [
+    // {
+    //   "ENV_VAR1": "ENV_VAR1_VALUE"
+    // },
+    // {
+    //   "ENV_VAR2": "ENV_VAR2_VALUE",
+    //   "ENV_VAR3": "ENV_VAR3_VALUE"
+    // }
+    // ]
+    // "#;
+    // let form:Vec<HashMap<String, String>> = serde_json::from_str(&body_str).unwrap();
+    // println!("{:?}", form);
+}
diff --git a/tests/web-item.json b/tests/web-item.json
new file mode 100644
index 00000000..cb4efb1a
--- /dev/null
+++ b/tests/web-item.json
@@ -0,0 +1,99 @@
+{
+  "_etag": null,
+  "_id": 180,
+  "_created": "2021-12-17T08:11:40.875486",
+  "_updated": "2023-11-21T17:37:24.594545",
+  "name": "Airflow",
+  "code": "airflow",
+  "role": [
+    "airflow"
+  ],
+  "type": "web",
+  "default": true,
+  "popularity": null,
+  "descr": null,
+  "ports": {
+    "private": [
+      "31"
+    ]
+  },
+  "commercial": null,
+  "subscription": null,
+  "autodeploy": null,
+  "suggested": null,
+  "dependency": null,
+  "avoid_render": null,
+  "price": null,
+  "icon": {
+    "light": {
+      "width": 150,
+      "height": 150,
+      "image": "7f41d873-2de5-4c6e-a037-42eeff572db6.svg"
+    },
+    "dark": {
+    }
+  },
+  "category_id": null,
+  "parent_app_id": null,
+  "full_description": null,
+  "description": "<p class=\"content-editor__unstyled\">Airflow description </p>",
+  "plan_type": null,
+  "ansible_var": null,
+  "repo_dir": null,
+  "cpu": "0.0",
+  "ram_size": null,
+  "disk_size": null,
+  "dockerhub_image": "airflow",
+  "form": null,
+  "category": [
+    null
+  ],
+  "group": [
+  ],
+  "versions": [
+    {
+      "_id": 425,
+      "name": "Airflow",
+      "version": "2.7.3",
+      "update_status": "published",
+      "tag": "latest"
+    },
+    {
+      "_id": 426,
+      "name": "Airflow",
+      "version": "2.7.1-unstable",
+      "update_status": "published",
+      "tag": "stable"
+    }
+  ],
+  "links": [
+  ],
+  "domain": "latestports.com",
+  "sharedPorts": [
+    {
+      "host_port": "5000",
+      "container_port": "5000"
+    }
+  ],
+  "main": true,
+  "version": {
+    "_id": 426,
+    "name": "Airflow",
+    "version": "2.7.1-unstable",
+    "update_status": "published",
+    "tag": "stable"
+  },
+  "environment": [
+    {
+      "ENV_VAR1": "ENV_VAR1_VALUE"
+    }
+  ],
+  "network": "testnetwork",
+  "restart": "always",
+  "volumes": [
+    {
+      "host_path": "/airflow/host/path/",
+      "container_path": "/airflow/container/path"
+    }
+  ]
+}
\ No newline at end of file

From 28938e65891433fcab9dfc00284b76039bdba6f0 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Mon, 4 Dec 2023 10:40:11 +0200
Subject: [PATCH 033/284] ports deserialize fix, small optimizations

---
 src/forms/stack.rs                 |  18 +-
 src/helpers/stack/builder.rs       | 264 ++++++++++++++---------------
 tests/custom-stack-payload-11.json |   1 +
 3 files changed, 140 insertions(+), 143 deletions(-)
 create mode 100644 tests/custom-stack-payload-11.json

diff --git a/src/forms/stack.rs b/src/forms/stack.rs
index 91b34cbc..18879311 100644
--- a/src/forms/stack.rs
+++ b/src/forms/stack.rs
@@ -34,14 +34,14 @@ pub struct Port {
     pub container_port: Option<String>
 }
 
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct Ports {
-    #[serde(rename(deserialize = "sharedPorts"))]
-    #[serde(rename(serialize = "shared_ports"))]
-    // #[serde(alias = "shared_ports")]
-    pub shared_ports: Option<Vec<Port>>,
-    pub ports: Option<Vec<String>>,
-}
+// #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+// pub struct Ports {
+//     #[serde(rename(deserialize = "sharedPorts"))]
+//     #[serde(rename(serialize = "shared_ports"))]
+//     // #[serde(alias = "shared_ports")]
+//     pub shared_ports: Option<Vec<Port>>,
+//     pub ports: Option<Vec<String>>,
+// }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct DockerImage {
@@ -258,7 +258,7 @@ pub struct App {
     // pub ports: Ports,
     #[serde(rename(deserialize = "sharedPorts"))]
     #[serde(rename(serialize = "shared_ports"))]
-    // #[serde(alias = "shared_ports")]
+    #[serde(alias = "shared_ports")]
     pub ports: Option<Vec<Port>>,
 }
 
diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index 316a03fe..00262575 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -37,18 +37,25 @@ pub struct DcBuilder {
     pub(crate) stack: Stack
 }
 
-// impl TryInto<Vec<Port>> for stack::Ports {
-//     type Error = String;
-//     fn try_into(self) -> Result<Vec<Port>, Self::Error> {
-//         convert_shared_ports(self.shared_ports.clone())
-//     }
-// }
 
-
-impl TryInto<Vec<Port>> for stack::Ports {
+impl TryInto<Port> for &stack::Port {
     type Error = String;
-    fn try_into(self) -> Result<Vec<Port>, Self::Error> {
-        convert_shared_ports(self.shared_ports.clone())
+    fn try_into(self) -> Result<Port, Self::Error> {
+        let cp  = self.container_port.clone()
+            .unwrap_or("".to_string())
+            .parse::<u16>().map_err(|err| "Could not parse port".to_string() )?;
+        let hp = self.host_port.clone()
+            .unwrap_or("".to_string())
+            .parse::<u16>().map_err(|err| "Could not parse port".to_string() )?;
+
+        tracing::debug!("Port conversion result: cp: {:?} hp: {:?}", cp, hp);
+        Ok(Port {
+            target: cp,
+            host_ip: None,
+            published: Some(PublishedPort::Single(hp)),
+            protocol: None,
+            mode: None,
+        })
     }
 }
 
@@ -60,23 +67,6 @@ fn convert_shared_ports(ports: Option<Vec<stack::Port>>) -> Result<Vec<Port>, St
         Some(ports) => {
             tracing::debug!("Ports >>>> {:?}", ports);
             for port in ports {
-                let cp  = port.container_port
-                    .unwrap_or("".to_string())
-                    .parse::<u16>().map_err(|err| "Could not parse port".to_string() )?;
-                let hp = port.host_port
-                    .unwrap_or("".to_string())
-                    .parse::<u16>().map_err(|err| "Could not parse port".to_string() )?;
-
-                tracing::debug!("Port conversion result: cp: {:?} hp: {:?}", cp, hp);
-                _ports.push(
-                    Port {
-                        target: cp,
-                        host_ip: None,
-                        published: Some(PublishedPort::Single(hp)),
-                        protocol: None,
-                        mode: None,
-                    }
-                );
             }
         }
         None => {
@@ -100,112 +90,118 @@ impl DcBuilder {
 
 
     pub fn build(&self) -> Option<String> {
-        //
-        // tracing::debug!("Start build docker compose from {:?}", &self.stack.body);
-        // let _stack = serde_json::from_value::<StackForm>(self.stack.body.clone());
-        // let mut services = indexmap::IndexMap::new();
-        // match _stack  {
-        //     Ok(apps) => {
-        //         // tracing::debug!("stack item {:?}", apps.custom.web);
-        //
-        //         for app_type in apps.custom.web {
-        //             let code = app_type.app.code.clone().to_owned();
-        //             let mut service = Service {
-        //                 image: Some(app_type.app.docker_image.to_string()),
-        //                 ..Default::default()
-        //             };
-        //
-        //             service.ports = Ports::Long(app_type.app.ports.unwrap().try_into().unwrap());
-        //             service.restart = Some("always".to_owned());
-        //             tracing::debug!("service 1 {:?}", &service);
-        //             services.insert(
-        //                 code,
-        //                 Some(service),
-        //             );
-        //         }
-        //
-        //
-        //         if let Some(srvs) = apps.custom.service {
-        //
-        //             if !srvs.is_empty() {
-        //
-        //                 for app_type in srvs {
-        //                     let code = app_type.app.code.to_owned();
-        //                     let mut service = Service {
-        //                         image: Some(app_type.app.docker_image.to_string()),
-        //                         ..Default::default()
-        //                     };
-        //
-        //                     // if let Some(ports) = &app_type.app._ports {
-        //                     //     tracing::debug!("service2 ports {:?}", ports);
-        //                     //     service.ports = Ports::Long(ports.try_into().unwrap())
-        //                     // }
-        //
-        //                     service.ports = Ports::Long(app_type.app.ports.unwrap().try_into().unwrap());
-        //                     service.restart = Some("always".to_owned());
-        //                     services.insert(
-        //                         code,
-        //                         Some(service),
-        //                     );
-        //                 }
-        //                 // tracing::debug!("services {:?}", services);
-        //             }
-        //         }
-        //
-        //         if let Some(features) = apps.custom.feature {
-        //
-        //             if !features.is_empty() {
-        //
-        //                 for app_type in features {
-        //                     let code = app_type.app.code.to_owned();
-        //                     let mut service = Service {
-        //                         // image: Some(app.dockerhub_image.as_ref().unwrap().to_owned()),
-        //                         image: Some(app_type.app.docker_image.to_string()),
-        //                         ..Default::default()
-        //                     };
-        //
-        //                     service.ports = Ports::Long(app_type.app.ports.unwrap().try_into().unwrap());
-        //                     // if let Some(ports) = &app_type.app._ports {
-        //                     //     tracing::debug!("service3 ports {:?}", ports);
-        //                     //     service.ports = Ports::Long(ports.try_into().unwrap())
-        //                     // }
-        //                     service.restart = Some("always".to_owned());
-        //                     services.insert(
-        //                         code,
-        //                         Some(service),
-        //                     );
-        //                 }
-        //                 // tracing::debug!("services### {:?}", &service);
-        //             }
-        //         }
-        //         tracing::debug!("services {:?}", &services);
-        //     }
-        //     Err(e) => {
-        //         tracing::debug!("Unpack stack form error {:?}", e);
-        //         ()
-        //     }
-        // }
-        //
-        // let compose_content = Compose {
-        //     version: Some("3.8".to_string()),
-        //     services: {
-        //         Services(services)
-        //     },
-        //     ..Default::default()
-        // };
-        //
-        // let fname= format!("./files/{}.yml", self.stack.stack_id);
-        // tracing::debug!("Saving docker compose to file {:?}", fname);
-        // let target_file = std::path::Path::new(fname.as_str());
-        // // serialize to string
-        // let serialized = match serde_yaml::to_string(&compose_content) {
-        //     Ok(s) => s,
-        //     Err(e) => panic!("Failed to serialize docker-compose file: {}", e),
-        // };
-        // // serialize to file
-        // std::fs::write(target_file, serialized.clone()).unwrap();
-        //
-        // Some(serialized)
-        None
+
+        tracing::debug!("Start build docker compose from {:?}", &self.stack.body);
+        let _stack = serde_json::from_value::<StackForm>(self.stack.body.clone());
+        let mut services = indexmap::IndexMap::new();
+        match _stack  {
+            Ok(apps) => {
+                // tracing::debug!("stack item {:?}", apps.custom.web);
+
+                for app_type in apps.custom.web {
+                    let code = app_type.app.code.clone().to_owned();
+                    let mut service = Service {
+                        image: Some(app_type.app.docker_image.to_string()),
+                        ..Default::default()
+                    };
+
+                    let ports: Vec<Port> = app_type.app.ports
+                        .unwrap()
+                        .iter()
+                        .map(|x| x.try_into().unwrap())
+                        .collect();
+                    service.ports = Ports::Long(ports);
+                    service.restart = Some("always".to_owned());
+                    tracing::debug!("service 1 {:?}", &service);
+                    services.insert(
+                        code,
+                        Some(service),
+                    );
+                }
+
+
+                if let Some(srvs) = apps.custom.service {
+
+                    if !srvs.is_empty() {
+
+                        for app_type in srvs {
+                            let code = app_type.app.code.to_owned();
+                            let mut service = Service {
+                                image: Some(app_type.app.docker_image.to_string()),
+                                ..Default::default()
+                            };
+
+                            let ports: Vec<Port> = app_type.app.ports
+                                .unwrap()
+                                .iter()
+                                .map(|x| x.try_into().unwrap())
+                                .collect();
+
+                            service.ports = Ports::Long(ports);
+                            service.restart = Some("always".to_owned());
+                            services.insert(
+                                code,
+                                Some(service),
+                            );
+                        }
+                        // tracing::debug!("services {:?}", services);
+                    }
+                }
+
+                if let Some(features) = apps.custom.feature {
+
+                    if !features.is_empty() {
+
+                        for app_type in features {
+                            let code = app_type.app.code.to_owned();
+                            let mut service = Service {
+                                // image: Some(app.dockerhub_image.as_ref().unwrap().to_owned()),
+                                image: Some(app_type.app.docker_image.to_string()),
+                                ..Default::default()
+                            };
+
+                            let ports: Vec<Port> = app_type.app.ports
+                                .unwrap()
+                                .iter()
+                                .map(|x| x.try_into().unwrap())
+                                .collect();
+                            service.ports = Ports::Long(ports);
+                            service.restart = Some("always".to_owned());
+                            services.insert(
+                                code,
+                                Some(service),
+                            );
+                        }
+                        // tracing::debug!("services### {:?}", &service);
+                    }
+                }
+                tracing::debug!("services {:?}", &services);
+            }
+            Err(e) => {
+                tracing::debug!("Unpack stack form error {:?}", e);
+                ()
+            }
+        }
+
+        let compose_content = Compose {
+            version: Some("3.8".to_string()),
+            services: {
+                Services(services)
+            },
+            ..Default::default()
+        };
+
+        let fname= format!("./files/{}.yml", self.stack.stack_id);
+        tracing::debug!("Saving docker compose to file {:?}", fname);
+        let target_file = std::path::Path::new(fname.as_str());
+        // serialize to string
+        let serialized = match serde_yaml::to_string(&compose_content) {
+            Ok(s) => s,
+            Err(e) => panic!("Failed to serialize docker-compose file: {}", e),
+        };
+        // serialize to file
+        std::fs::write(target_file, serialized.clone()).unwrap();
+
+        Some(serialized)
     }
 }
diff --git a/tests/custom-stack-payload-11.json b/tests/custom-stack-payload-11.json
new file mode 100644
index 00000000..ee27be48
--- /dev/null
+++ b/tests/custom-stack-payload-11.json
@@ -0,0 +1 @@
+{"commonDomain":"","domainList":{},"region":"fsn1","zone":null,"server":"cx21","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":["portainer_ce_feature"],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":true,"disk_type":"pd-standart","cloud_token":"****","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"_etag":null,"_id":180,"_created":"2021-12-17T08:11:40.875486","_updated":"2023-11-21T17:37:24.594545","name":"Airflow","code":"airflow","role":["airflow"],"type":"web","default":true,"popularity":null,"descr":null,"ports":{"private":["31"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":150,"height":150,"image":"7f41d873-2de5-4c6e-a037-42eeff572db6.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\">Airflow description </p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.0","ram_size":null,"disk_size":null,"dockerhub_image":"airflow","form":null,"category":[null],"group":[],"versions":[{"_id":425,"name":"Airflow","version":"2.7.3","update_status":"published","tag":"latest"},{"_id":426,"name":"Airflow","version":"2.7.1-unstable","update_status":"published","tag":"stable"}],"links":[],"domain":"latestports.com","sharedPorts":[{"host_port":"5000","container_port":"5000"}],"main":true,"version":{"_id":426,"name":"Airflow","version":"2.7.1-unstable","update_status":"published","tag":"stable"},"environment":[{"ENV_VAR1":"ENV_VAR1_VALUE"}],"network":"testnetwork","restart":"always","volumes":[{"host_path":"/airflow/host/path/","container_path":"/airflow/container/path"}]},{"category":[{"name":"integrated_features"}],"name":"CustomNginx","code":"customnginx","domain":"customnginx.latestports.com","sharedPorts":[{"host_port":"80","container_port":"8080"},{"host_port":"443","container_port":"8443"}],"versions":[],"custom":true,"type":"web","main":false,"_id":0,"dockerhub_user":"trydirect","dockerhub_name":"nginx","disk_size":"0.9Gb","ram_size":"1.3Gb","cpu":"0.3","volumes":[{"host_path":"asdfasdf","container_path":"asdfasdf"}],"environment":[{"CUSTOM_NGINX_ENV_VAR1":"CUSTOM_NGINX_ENV_VAR1_VALUE"},{"/customnginx/host/path":"/customnginx/container/path"}],"network":"testnetwork","restart":"always"}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\"><strong style=\"font-weight:bold\">Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)</strong></p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.6","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","form":null,"category":[{"name":"integrated_features"}],"group":[],"versions":[{"_id":456,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"}],"links":[],"version":{"_id":456,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"},"sharedPorts":[{"host_port":"9000","container_port":"9000"}],"environment":[{"PORTAINER_ENV_VAR1":"PORTAINER_ENV_VAR1_VALUE"}],"volumes":[{"host_path":"/portainer/test/path","container_path":"/portainer/test/path"}],"network":"testnetwork","restart":"always"}],"service":[{"_etag":null,"_id":24,"_created":"2020-06-19T13:07:24.228389","_updated":"2023-11-23T10:18:28.095813","name":"PostgreSQL","code":"postgres","role":["postgres"],"type":"service","default":null,"popularity":null,"descr":null,"ports":{"private":["5432"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":576,"height":594,"image":"fd23f54c-e250-4228-8d56-7e5d93ffb925.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":null,"plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.0","ram_size":null,"disk_size":null,"dockerhub_image":"postgres","form":null,"category":[null],"group":[{"_etag":null,"_id":12,"_created":null,"_updated":null,"name":"Database","code":"database"}],"versions":[{"_id":458,"name":"15","version":"15","update_status":"published","tag":"15"},{"_id":288,"name":"10.22","version":"10.22","update_status":"published","tag":"10.22"},{"_id":303,"name":"13.8","version":"13.8","update_status":"published","tag":"13.8"},{"_id":266,"name":"11","version":"11","update_status":"published","tag":"11"},{"_id":267,"name":"12.12","version":"12.12","update_status":"published","tag":"12.12"},{"_id":38,"name":"14.5","version":"14.5","update_status":"published","tag":"14.5"},{"_id":596,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"}],"links":[],"domain":"","sharedPorts":[{"host_port":"5432","container_port":"5432"}],"main":false,"version":{"_id":596,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"},"environment":[{"POSTGRES_ENV_VAR1":"POSTGRES_ENV_VAR1_VALUE"},{"POSTGRES_ENV_VAR2":"POSTGRES_ENV_VAR2_VALUE"}],"network":"testnetwork","restart":"always","volumes":[{"host_path":"./pg.conf","container_path":"/etc/postgres/pg.conf"}]}],"servers_count":3,"project_name":"LatestPorts","custom_stack_code":"latestports","project_git_url":"https://github.com/latestports","custom_stack_category":[],"networks":["testnetwork","testnetwork2"]}}
\ No newline at end of file

From be7917146170296c3923e9f0cd6a82be0d14b493 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Mon, 4 Dec 2023 16:53:59 +0200
Subject: [PATCH 034/284] stack crud, update impl.

---
 .gitignore                         |   2 +-
 src/forms/stack.rs                 |   4 +-
 src/helpers/json.rs                |   3 +-
 src/routes/stack/add.rs            |  10 ++-
 src/routes/stack/compose.rs        |   2 +-
 src/routes/stack/update.rs         | 117 ++++++++++++++++++++++++++++-
 src/startup.rs                     |   3 +-
 tests/custom-stack-payload-11.json |   2 +-
 8 files changed, 130 insertions(+), 13 deletions(-)

diff --git a/.gitignore b/.gitignore
index 3acd8afb..32da6d82 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,3 @@
 target
-.idea
+.idea/
 files
diff --git a/src/forms/stack.rs b/src/forms/stack.rs
index 18879311..544dc3df 100644
--- a/src/forms/stack.rs
+++ b/src/forms/stack.rs
@@ -79,8 +79,8 @@ impl AsRef<DockerImage> for App {
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct StackForm {
-    #[validate(min_length=2)]
-    #[validate(max_length=255)]
+    // #[validate(min_length=2)]
+    // #[validate(max_length=255)]
     #[serde(rename= "commonDomain")]
     pub common_domain: Option<String>,
     pub domain_list: Option<DomainList>,
diff --git a/src/helpers/json.rs b/src/helpers/json.rs
index 6a08405b..a310ab37 100644
--- a/src/helpers/json.rs
+++ b/src/helpers/json.rs
@@ -9,7 +9,8 @@ pub(crate) struct JsonResponse<T> {
     pub(crate) message: String,
     pub(crate) id: Option<i32>,
     pub(crate) item: Option<T>,
-    pub(crate) list: Option<Vec<T>>
+    pub(crate) list: Option<Vec<T>>,
+    // pub(crate) form: Option<T>
 }
 
 #[derive(Serialize, Default)]
diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs
index 7c45ef55..b793925d 100644
--- a/src/routes/stack/add.rs
+++ b/src/routes/stack/add.rs
@@ -25,6 +25,7 @@ pub async fn add(
     pool: Data<PgPool>,
 ) -> Result<impl Responder> {
 
+    // @todo ACL
     let body_bytes = actix_web::body::to_bytes(body).await.unwrap();
     let body_str = str::from_utf8(&body_bytes).unwrap();
     let form = match serde_json::from_str::<StackForm>(body_str) {
@@ -82,9 +83,12 @@ pub async fn add(
 
     let query_span = tracing::info_span!("Saving new stack details into the database");
 
-    let errors = form.validate().unwrap_err();
-    tracing::debug!("{:?}",errors);
-
+    if !form.validate().is_ok() {
+        let errors = form.validate().unwrap_err();
+        let err_msg = format!("Invalid data received {:?}", &errors.to_string());
+        tracing::debug!(err_msg);
+        return JsonResponse::build().err(errors.to_string());// tmp solution
+    }
 
     let body: Value = match serde_json::to_value::<StackForm>(form) {
         Ok(body) => body,
diff --git a/src/routes/stack/compose.rs b/src/routes/stack/compose.rs
index 58f82f76..4dc3c7f1 100644
--- a/src/routes/stack/compose.rs
+++ b/src/routes/stack/compose.rs
@@ -15,7 +15,7 @@ use tracing::Instrument;
 use crate::helpers::stack::builder::DcBuilder;
 
 #[tracing::instrument(name = "User's generate docker-compose.")]
-#[post("/{id}")]
+#[get("/{id}")]
 pub async fn add(
     user: web::ReqData<User>,
     path: web::Path<(i32,)>,
diff --git a/src/routes/stack/update.rs b/src/routes/stack/update.rs
index 5a4fa0c2..56514ebe 100644
--- a/src/routes/stack/update.rs
+++ b/src/routes/stack/update.rs
@@ -1,4 +1,115 @@
-use actix_web::HttpResponse;
-pub async fn update() -> HttpResponse {
-    unimplemented!()
+use chrono::Utc;
+use actix_web::{
+    web,
+    web::{Data},
+    Responder, Result,
+};
+use crate::forms::stack::StackForm;
+use crate::helpers::JsonResponse;
+use crate::models::user::User;
+use actix_web::post;
+use serde_json::Value;
+use sqlx::PgPool;
+use serde_valid::Validate;
+use tracing::Instrument;
+use uuid::Uuid;
+use crate::models;
+
+
+#[tracing::instrument(name = "Update stack.")]
+#[post("/{id}")]
+pub async fn update(
+    path: web::Path<(i32,)>,
+    form: web::Json<StackForm>,
+    user: web::ReqData<User>,
+    pool: Data<PgPool>,
+) -> Result<impl Responder> {
+    // @todo ACL
+
+    let (id,) = path.into_inner();
+    let query_span = tracing::info_span!("Check existence by id.");
+    match sqlx::query_as!(
+        models::Stack,
+        r"SELECT * FROM user_stack WHERE id = $1",
+        id
+    )
+        .fetch_one(pool.get_ref())
+        .instrument(query_span)
+        .await
+    {
+        Ok(stack) => {
+            tracing::info!("Found record: {:?}", stack.id);
+        }
+        Err(e) => {
+            tracing::error!("Failed to fetch record: {:?}, error: {:?}", id, e);
+            return JsonResponse::<models::Stack>::build()
+                .err(format!("Object not found {}", id));
+        }
+    };
+
+    let stack_name = form.custom.custom_stack_code.clone();
+    tracing::debug!("form data: {:?}", form);
+    let user_id = user.id.clone();
+    let request_id = Uuid::new_v4();
+    let request_span = tracing::info_span!(
+        "Validating a stack", %request_id,
+        commonDomain=?&form.custom.project_name,
+        region=?&form.region,
+        domainList=?&form.domain_list
+    );
+    let _request_span_guard = request_span.enter(); // ->exit
+
+    tracing::info!("request_id {} Updating '{}' '{}'",
+        request_id,
+        form.custom.project_name,
+        form.region
+    );
+
+    let query_span = tracing::info_span!("Update stack details in db.");
+
+    if !form.validate().is_ok() {
+        let errors = form.validate().unwrap_err();
+        let err_msg = format!("Invalid data received {:?}", &errors.to_string());
+        tracing::debug!(err_msg);
+        return JsonResponse::build().err(errors.to_string());
+    }
+
+    let body: Value = match serde_json::to_value::<StackForm>(form.into_inner()) {
+        Ok(body) => body,
+        Err(err) => {
+            tracing::error!("Request_id {} error unwrap body {:?}", request_id, err);
+            serde_json::to_value::<StackForm>(StackForm::default()).unwrap()
+        }
+    };
+
+    match sqlx::query!(
+        r#"
+        UPDATE user_stack
+        SET stack_id=$2, user_id=$3, name=$4, body=$5, created_at=$6, updated_at=$7
+        WHERE id=$1
+        "#,
+        id,
+        Uuid::new_v4(),
+        user_id,
+        stack_name,
+        body,
+        Utc::now(),
+        Utc::now(),
+    )
+        .execute(pool.get_ref())
+        .instrument(query_span)
+        .await
+    {
+        Ok(record) => {
+            tracing::info!(
+                "req_id: {} stack details have been saved to database",
+                request_id
+            );
+            return JsonResponse::build().set_id(id).ok("OK".to_owned());
+        }
+        Err(e) => {
+            tracing::error!("req_id: {} Failed to execute query: {:?}", request_id, e);
+            return JsonResponse::build().err("Internal Server Error".to_owned());
+        }
+    };
 }
diff --git a/src/startup.rs b/src/startup.rs
index 3d38420a..816255ff 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -67,7 +67,8 @@ pub async fn run(
                     .service(crate::routes::stack::compose::admin)
                     .service(crate::routes::stack::get::item)
                     .service(crate::routes::stack::get::list)
-                    .service(crate::routes::stack::add::add),
+                    .service(crate::routes::stack::add::add)
+                    .service(crate::routes::stack::update::update),
             )
             .app_data(db_pool.clone())
             .app_data(settings.clone())
diff --git a/tests/custom-stack-payload-11.json b/tests/custom-stack-payload-11.json
index ee27be48..d87ce41e 100644
--- a/tests/custom-stack-payload-11.json
+++ b/tests/custom-stack-payload-11.json
@@ -1 +1 @@
-{"commonDomain":"","domainList":{},"region":"fsn1","zone":null,"server":"cx21","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":["portainer_ce_feature"],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":true,"disk_type":"pd-standart","cloud_token":"****","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"_etag":null,"_id":180,"_created":"2021-12-17T08:11:40.875486","_updated":"2023-11-21T17:37:24.594545","name":"Airflow","code":"airflow","role":["airflow"],"type":"web","default":true,"popularity":null,"descr":null,"ports":{"private":["31"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":150,"height":150,"image":"7f41d873-2de5-4c6e-a037-42eeff572db6.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\">Airflow description </p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.0","ram_size":null,"disk_size":null,"dockerhub_image":"airflow","form":null,"category":[null],"group":[],"versions":[{"_id":425,"name":"Airflow","version":"2.7.3","update_status":"published","tag":"latest"},{"_id":426,"name":"Airflow","version":"2.7.1-unstable","update_status":"published","tag":"stable"}],"links":[],"domain":"latestports.com","sharedPorts":[{"host_port":"5000","container_port":"5000"}],"main":true,"version":{"_id":426,"name":"Airflow","version":"2.7.1-unstable","update_status":"published","tag":"stable"},"environment":[{"ENV_VAR1":"ENV_VAR1_VALUE"}],"network":"testnetwork","restart":"always","volumes":[{"host_path":"/airflow/host/path/","container_path":"/airflow/container/path"}]},{"category":[{"name":"integrated_features"}],"name":"CustomNginx","code":"customnginx","domain":"customnginx.latestports.com","sharedPorts":[{"host_port":"80","container_port":"8080"},{"host_port":"443","container_port":"8443"}],"versions":[],"custom":true,"type":"web","main":false,"_id":0,"dockerhub_user":"trydirect","dockerhub_name":"nginx","disk_size":"0.9Gb","ram_size":"1.3Gb","cpu":"0.3","volumes":[{"host_path":"asdfasdf","container_path":"asdfasdf"}],"environment":[{"CUSTOM_NGINX_ENV_VAR1":"CUSTOM_NGINX_ENV_VAR1_VALUE"},{"/customnginx/host/path":"/customnginx/container/path"}],"network":"testnetwork","restart":"always"}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\"><strong style=\"font-weight:bold\">Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)</strong></p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.6","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","form":null,"category":[{"name":"integrated_features"}],"group":[],"versions":[{"_id":456,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"}],"links":[],"version":{"_id":456,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"},"sharedPorts":[{"host_port":"9000","container_port":"9000"}],"environment":[{"PORTAINER_ENV_VAR1":"PORTAINER_ENV_VAR1_VALUE"}],"volumes":[{"host_path":"/portainer/test/path","container_path":"/portainer/test/path"}],"network":"testnetwork","restart":"always"}],"service":[{"_etag":null,"_id":24,"_created":"2020-06-19T13:07:24.228389","_updated":"2023-11-23T10:18:28.095813","name":"PostgreSQL","code":"postgres","role":["postgres"],"type":"service","default":null,"popularity":null,"descr":null,"ports":{"private":["5432"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":576,"height":594,"image":"fd23f54c-e250-4228-8d56-7e5d93ffb925.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":null,"plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.0","ram_size":null,"disk_size":null,"dockerhub_image":"postgres","form":null,"category":[null],"group":[{"_etag":null,"_id":12,"_created":null,"_updated":null,"name":"Database","code":"database"}],"versions":[{"_id":458,"name":"15","version":"15","update_status":"published","tag":"15"},{"_id":288,"name":"10.22","version":"10.22","update_status":"published","tag":"10.22"},{"_id":303,"name":"13.8","version":"13.8","update_status":"published","tag":"13.8"},{"_id":266,"name":"11","version":"11","update_status":"published","tag":"11"},{"_id":267,"name":"12.12","version":"12.12","update_status":"published","tag":"12.12"},{"_id":38,"name":"14.5","version":"14.5","update_status":"published","tag":"14.5"},{"_id":596,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"}],"links":[],"domain":"","sharedPorts":[{"host_port":"5432","container_port":"5432"}],"main":false,"version":{"_id":596,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"},"environment":[{"POSTGRES_ENV_VAR1":"POSTGRES_ENV_VAR1_VALUE"},{"POSTGRES_ENV_VAR2":"POSTGRES_ENV_VAR2_VALUE"}],"network":"testnetwork","restart":"always","volumes":[{"host_path":"./pg.conf","container_path":"/etc/postgres/pg.conf"}]}],"servers_count":3,"project_name":"LatestPorts","custom_stack_code":"latestports","project_git_url":"https://github.com/latestports","custom_stack_category":[],"networks":["testnetwork","testnetwork2"]}}
\ No newline at end of file
+{"commonDomain":"justates222t.com","domainList":{},"region":"fsn1","zone":null,"server":"cx21","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":["portainer_ce_feature"],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":true,"disk_type":"pd-standart","cloud_token":"**********","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"_etag":null,"_id":180,"_created":"2021-12-17T08:11:40.875486","_updated":"2023-11-21T17:37:24.594545","name":"Airflow","code":"airflow","role":["airflow"],"type":"web","default":true,"popularity":null,"descr":null,"ports":{"private":["31"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":150,"height":150,"image":"7f41d873-2de5-4c6e-a037-42eeff572db6.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\">Airflow description </p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.0","ram_size":null,"disk_size":null,"dockerhub_image":"airflow","form":null,"category":[null],"group":[],"versions":[{"_id":425,"name":"Airflow","version":"2.7.3","update_status":"published","tag":"latest"},{"_id":426,"name":"Airflow","version":"2.7.1-unstable","update_status":"published","tag":"stable"}],"links":[],"domain":"latestports.com","sharedPorts":[{"host_port":"5000","container_port":"5000"}],"main":true,"version":{"_id":426,"name":"Airflow","version":"2.7.1-unstable","update_status":"published","tag":"stable"},"environment":[{"ENV_VAR1":"ENV_VAR1_VALUE"}],"network":"testnetwork","restart":"always","volumes":[{"host_path":"/airflow/host/path/","container_path":"/airflow/container/path"}]},{"category":[{"name":"integrated_features"}],"name":"CustomNginx","code":"customnginx","domain":"customnginx.latestports.com","sharedPorts":[{"host_port":"80","container_port":"8080"},{"host_port":"443","container_port":"8443"}],"versions":[],"custom":true,"type":"web","main":false,"_id":0,"dockerhub_user":"trydirect","dockerhub_name":"nginx","disk_size":"0.9Gb","ram_size":"1.3Gb","cpu":"0.3","volumes":[{"host_path":"asdfasdf","container_path":"asdfasdf"}],"environment":[{"CUSTOM_NGINX_ENV_VAR1":"CUSTOM_NGINX_ENV_VAR1_VALUE"},{"/customnginx/host/path":"/customnginx/container/path"}],"network":"testnetwork","restart":"always"}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\"><strong style=\"font-weight:bold\">Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)</strong></p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.6","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","form":null,"category":[{"name":"integrated_features"}],"group":[],"versions":[{"_id":456,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"}],"links":[],"version":{"_id":456,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"},"sharedPorts":[{"host_port":"9000","container_port":"9000"}],"environment":[{"PORTAINER_ENV_VAR1":"PORTAINER_ENV_VAR1_VALUE"}],"volumes":[{"host_path":"/portainer/test/path","container_path":"/portainer/test/path"}],"network":"testnetwork","restart":"always"}],"service":[{"_etag":null,"_id":24,"_created":"2020-06-19T13:07:24.228389","_updated":"2023-11-23T10:18:28.095813","name":"PostgreSQL","code":"postgres","role":["postgres"],"type":"service","default":null,"popularity":null,"descr":null,"ports":{"private":["5432"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":576,"height":594,"image":"fd23f54c-e250-4228-8d56-7e5d93ffb925.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":null,"plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.0","ram_size":null,"disk_size":null,"dockerhub_image":"postgres","form":null,"category":[null],"group":[{"_etag":null,"_id":12,"_created":null,"_updated":null,"name":"Database","code":"database"}],"versions":[{"_id":458,"name":"15","version":"15","update_status":"published","tag":"15"},{"_id":288,"name":"10.22","version":"10.22","update_status":"published","tag":"10.22"},{"_id":303,"name":"13.8","version":"13.8","update_status":"published","tag":"13.8"},{"_id":266,"name":"11","version":"11","update_status":"published","tag":"11"},{"_id":267,"name":"12.12","version":"12.12","update_status":"published","tag":"12.12"},{"_id":38,"name":"14.5","version":"14.5","update_status":"published","tag":"14.5"},{"_id":596,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"}],"links":[],"domain":"","sharedPorts":[{"host_port":"5432","container_port":"5432"}],"main":false,"version":{"_id":596,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"},"environment":[{"POSTGRES_ENV_VAR1":"POSTGRES_ENV_VAR1_VALUE"},{"POSTGRES_ENV_VAR2":"POSTGRES_ENV_VAR2_VALUE"}],"network":"testnetwork","restart":"always","volumes":[{"host_path":"./pg.conf","container_path":"/etc/postgres/pg.conf"}]}],"servers_count":3,"project_name":"LatestPorts","custom_stack_code":"latestports","project_git_url":"https://github.com/latestports","custom_stack_category":[],"networks":["testnetwork","testnetwork2"]}}
\ No newline at end of file

From d71533b4c4f84aa71825ceda7148d8fec3c539a5 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Mon, 4 Dec 2023 17:03:12 +0200
Subject: [PATCH 035/284] issue-auth test/deploy JsonResponse

---
 src/routes/test/deploy.rs | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/src/routes/test/deploy.rs b/src/routes/test/deploy.rs
index 7a8eddf0..51cc2014 100644
--- a/src/routes/test/deploy.rs
+++ b/src/routes/test/deploy.rs
@@ -2,6 +2,7 @@ use crate::models::Client;
 use actix_web::{post, web, Responder, Result};
 use serde::Serialize;
 use std::sync::Arc;
+use crate::helpers::JsonResponse;
 
 #[derive(Serialize)]
 struct DeployResponse {
@@ -12,8 +13,5 @@ struct DeployResponse {
 #[tracing::instrument(name = "Test deploy.")]
 #[post("/deploy")]
 pub async fn handler(client: web::ReqData<Arc<Client>>) -> Result<impl Responder> {
-    Ok(web::Json(DeployResponse {
-        status: "success".to_string(),
-        client: client.into_inner(),
-    }))
+    JsonResponse::build().set_item(client.into_inner()).ok("success")
 }

From a0d72391c24bd8c9d36d8534e475c95b330a7977 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Mon, 4 Dec 2023 17:42:13 +0200
Subject: [PATCH 036/284] issue-auth optimising middleware client

---
 src/middleware/trydirect.rs | 99 ++++++++++++++++++++-----------------
 tests/middleware_client.rs  |  4 ++
 2 files changed, 59 insertions(+), 44 deletions(-)

diff --git a/src/middleware/trydirect.rs b/src/middleware/trydirect.rs
index 755baba2..cf5079b2 100644
--- a/src/middleware/trydirect.rs
+++ b/src/middleware/trydirect.rs
@@ -1,4 +1,5 @@
 use crate::configuration::Settings;
+use futures::future::{FutureExt};
 use crate::forms::user::UserForm;
 use actix_web::dev::ServiceRequest;
 use actix_web::error::{ErrorInternalServerError, ErrorUnauthorized};
@@ -7,59 +8,69 @@ use actix_web::Error;
 use actix_web::HttpMessage;
 use actix_web_httpauth::extractors::bearer::BearerAuth;
 use reqwest::header::{ACCEPT, CONTENT_TYPE};
+use crate::helpers::JsonResponse;
 
-use crate::models::user::User;
+use crate::models::user::User; //todo
 
 #[tracing::instrument(name = "Trydirect bearer guard.")]
 pub async fn bearer_guard(
     req: ServiceRequest,
     credentials: BearerAuth,
-) -> Result<ServiceRequest, (Error, ServiceRequest)> {
-    let settings = req.app_data::<web::Data<Settings>>().unwrap();
-    let client = reqwest::Client::new();
-    let resp = client
-        .get(&settings.auth_url)
-        .bearer_auth(credentials.token())
-        .header(CONTENT_TYPE, "application/json")
-        .header(ACCEPT, "application/json")
-        .send()
-        .await;
+    ) -> Result<ServiceRequest, (Error, ServiceRequest)> {
+    async {
+        let settings = req.app_data::<web::Data<Settings>>().unwrap();
+        let client = reqwest::Client::new();
+        let resp = client
+            .get(&settings.auth_url)
+            .bearer_auth(credentials.token())
+            .header(CONTENT_TYPE, "application/json")
+            .header(ACCEPT, "application/json")
+            .send()
+            .await;
 
-    let resp = match resp {
-        Ok(resp) if resp.status().is_success() => resp,
-        Ok(resp) => {
-            tracing::error!("Authentication service returned no success {:?}", resp);
-            return Err((ErrorUnauthorized("401 Unauthorized"), req));
-        }
-        Err(err) => {
-            tracing::error!("error from reqwest {:?}", err);
-            return Err((ErrorInternalServerError(err.to_string()), req));
-        }
-    };
+        let resp = match resp {
+            Ok(resp) if resp.status().is_success() => resp,
+            Ok(resp) => {
+                tracing::error!("Authentication service returned no success {:?}", resp);
+                return Err(("401 Unauthorized".to_string(), req));
+            }
+            Err(err) => {
+                tracing::error!("error from reqwest {:?}", err);
+                return Err((err.to_string(), req));
+            }
+        };
 
-    let user_form: UserForm = match resp.json().await {
-        Ok(user) => {
-            tracing::info!("unpacked user {user:?}");
-            user
-        }
-        Err(err) => {
-            tracing::error!("can't parse the response body {:?}", err);
-            return Err((ErrorUnauthorized(""), req));
-        }
-    };
+        let user_form: UserForm = match resp.json().await {
+            Ok(user) => {
+                tracing::info!("unpacked user {user:?}");
+                user
+            }
+            Err(err) => {
+                tracing::error!("can't parse the response body {:?}", err);
+                return Err(("can't parse the response body".to_string(), req));
+            }
+        };
 
-    let user: User = match user_form.try_into() {
-        Ok(user) => user,
-        Err(err) => {
-            tracing::error!("Could not create User from form data: {:?}", err);
-            return Err((ErrorUnauthorized("Unauthorized"), req));
+        let user: User = match user_form.try_into() {
+            Ok(user) => user,
+            Err(err) => {
+                tracing::error!("Could not create User from form data: {:?}", err);
+                return Err(("Could not create User from form data".to_string(), req));
+            }
+        };
+        let existent_user = req.extensions_mut().insert(user);
+        if existent_user.is_some() {
+            tracing::error!("already logged {existent_user:?}");
+            return Err(("user already logged".to_string(), req));
         }
-    };
-    let existent_user = req.extensions_mut().insert(user);
-    if existent_user.is_some() {
-        tracing::error!("already logged {existent_user:?}");
-        return Err((ErrorInternalServerError(""), req));
-    }
 
-    Ok(req)
+        Ok(req)
+    }.await
+    .map_err(|(err, req)| {
+        tracing::error!("Authentication service returned no success {:?}", err);
+        (ErrorUnauthorized(JsonResponse::<i32>::build().set_msg(err).to_string()), req) //todo
+                                                                                        //default
+                                                                                        //type for
+                                                                                        //JsonResponse
+    })
 }
diff --git a/tests/middleware_client.rs b/tests/middleware_client.rs
index 31c55653..8f2a9f5b 100644
--- a/tests/middleware_client.rs
+++ b/tests/middleware_client.rs
@@ -19,4 +19,8 @@ async fn middleware_client_works() {
 
     assert!(response.status().is_success());
     assert_eq!(Some(0), response.content_length());
+
+
+    //todo header stacker-id not found
+    //
 }

From 4c76fc3de978b40aa0308dd3e0991ea5f0392778 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Mon, 4 Dec 2023 17:44:11 +0200
Subject: [PATCH 037/284] response shortened

---
 .idea/dataSources.xml        | 12 -----
 configuration.yaml           | 17 -------
 src/helpers/json.rs          | 96 +++++++++++-------------------------
 src/routes/client/add.rs     |  8 +--
 src/routes/client/disable.rs |  2 +-
 src/routes/client/enable.rs  |  2 +-
 src/routes/client/update.rs  |  2 +-
 src/routes/rating/add.rs     |  8 +--
 src/routes/rating/get.rs     | 10 ++--
 src/routes/stack/add.rs      | 10 ++--
 src/routes/stack/compose.rs  |  8 +--
 src/routes/stack/deploy.rs   |  4 +-
 src/routes/stack/get.rs      |  8 +--
 src/routes/stack/update.rs   |  8 +--
 tests/model_user_stack.rs    |  4 +-
 15 files changed, 67 insertions(+), 132 deletions(-)
 delete mode 100644 .idea/dataSources.xml
 delete mode 100644 configuration.yaml

diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml
deleted file mode 100644
index a9f67436..00000000
--- a/.idea/dataSources.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="DataSourceManagerImpl" format="xml" multifile-model="true">
-    <data-source source="LOCAL" name="stacker@localhost" uuid="9d3f3c7c-64ae-4723-bf7c-017581139950">
-      <driver-ref>postgresql</driver-ref>
-      <synchronize>true</synchronize>
-      <jdbc-driver>org.postgresql.Driver</jdbc-driver>
-      <jdbc-url>jdbc:postgresql://localhost:5432/stacker</jdbc-url>
-      <working-dir>$ProjectFileDir$</working-dir>
-    </data-source>
-  </component>
-</project>
\ No newline at end of file
diff --git a/configuration.yaml b/configuration.yaml
deleted file mode 100644
index 030dd49d..00000000
--- a/configuration.yaml
+++ /dev/null
@@ -1,17 +0,0 @@
-#auth_url: http://127.0.0.1:8080/me
-app_host: 127.0.0.1
-app_port: 8000
-auth_url: https://dev.try.direct/server/user/oauth_server/api/me
-max_clients_number: 2
-database:
-  host: 127.0.0.1
-  port: 5432
-  username: postgres
-  password: postgres
-  database_name: stacker
-
-amqp:
-  host: 127.0.0.1
-  port: 5672
-  username: guest
-  password: guest
diff --git a/src/helpers/json.rs b/src/helpers/json.rs
index a310ab37..8990788d 100644
--- a/src/helpers/json.rs
+++ b/src/helpers/json.rs
@@ -1,16 +1,18 @@
-use actix_web::error::{ErrorBadRequest, ErrorConflict, ErrorNotFound, ErrorInternalServerError};
-use serde_derive::Serialize;
+use actix_web::error::{ErrorBadRequest, ErrorConflict, ErrorInternalServerError, ErrorNotFound};
 use actix_web::web::Json;
 use actix_web::Error;
 use actix_web::Result;
+use serde_derive::Serialize;
 
 #[derive(Serialize)]
 pub(crate) struct JsonResponse<T> {
     pub(crate) message: String,
+    #[serde(skip_serializing_if = "Option::is_none")]
     pub(crate) id: Option<i32>,
+    #[serde(skip_serializing_if = "Option::is_none")]
     pub(crate) item: Option<T>,
+    #[serde(skip_serializing_if = "Option::is_none")]
     pub(crate) list: Option<Vec<T>>,
-    // pub(crate) form: Option<T>
 }
 
 #[derive(Serialize, Default)]
@@ -18,6 +20,7 @@ pub struct JsonResponseBuilder<T>
 where
     T: serde::Serialize + Default,
 {
+    message: String,
     id: Option<i32>,
     item: Option<T>,
     list: Option<Vec<T>>,
@@ -27,6 +30,11 @@ impl<T> JsonResponseBuilder<T>
 where
     T: serde::Serialize + Default,
 {
+    pub(crate) fn set_msg<I: Into<String>>(mut self, msg: I) -> Self {
+        self.message = msg.into();
+        self
+    }
+
     pub(crate) fn set_item(mut self, item: T) -> Self {
         self.item = Some(item);
         self
@@ -42,75 +50,44 @@ where
         self
     }
 
-    fn to_json_response(self, msg: String) -> JsonResponse<T> {
+    fn to_json_response(self) -> JsonResponse<T> {
         JsonResponse {
-            message: msg,
+            message: self.message,
             id: self.id,
             item: self.item,
             list: self.list,
         }
     }
 
-    pub(crate) fn ok<I: Into<String>>(self, msg: I) -> Result<Json<JsonResponse<T>>, Error> {
-        Ok(Json(self.to_json_response(msg.into())))
+    pub(crate) fn to_string(self) -> String {
+        let json_response = self.to_json_response();
+        serde_json::to_string(&json_response).unwrap()
     }
 
-    pub(crate) fn err<I: Into<String>>(self, msg: I) -> Result<Json<JsonResponse<T>>, Error> {
-        let json_response = self.to_json_response(msg.into());
-
-        Err(ErrorBadRequest(
-            serde_json::to_string(&json_response).unwrap(),
-        ))
-    }
-
-    pub(crate) fn not_found(self, msg: String) -> Result<Json<JsonResponse<T>>, Error>  {
-
-        let json_response = JsonResponse {
-            message: msg,
-            id: self.id,
-            item: self.item,
-            list: self.list
-        };
-
-        Err(ErrorNotFound(
-            serde_json::to_string(&json_response).unwrap()))
+    pub(crate) fn ok<I: Into<String>>(self, msg: I) -> Result<Json<JsonResponse<T>>, Error> {
+        Ok(Json(self.set_msg(msg).to_json_response()))
     }
 
-    pub(crate) fn internal_error(self, msg: String) -> Result<Json<JsonResponse<T>>, Error>  {
-
-        let json_response = JsonResponse {
-            message: msg,
-            id: self.id,
-            item: self.item,
-            list: self.list
-        };
-
-        Err(ErrorInternalServerError(
-            serde_json::to_string(&json_response).unwrap()))
+    pub(crate) fn bad_request<I: Into<String>>(
+        self,
+        msg: I,
+    ) -> Result<Json<JsonResponse<T>>, Error> {
+        Err(ErrorBadRequest(self.set_msg(msg).to_string()))
     }
 
-    pub(crate) fn conflict(self, msg: String) -> Result<Json<JsonResponse<T>>, Error>  {
-
-        let json_response = JsonResponse {
-            message: msg,
-            id: self.id,
-            item: self.item,
-            list: self.list
-        };
-
-        Err(ErrorConflict(
-            serde_json::to_string(&json_response).unwrap()))
+    pub(crate) fn not_found<I: Into<String>>(self, msg: I) -> Result<Json<JsonResponse<T>>, Error> {
+        Err(ErrorNotFound(self.set_msg(msg).to_string()))
     }
 
-    pub(crate) fn err_internal_server_error<I: Into<String>>(
+    pub(crate) fn internal_server_error<I: Into<String>>(
         self,
         msg: I,
     ) -> Result<Json<JsonResponse<T>>, Error> {
-        let json_response = self.to_json_response(msg.into());
+        Err(ErrorInternalServerError(self.set_msg(msg).to_string()))
+    }
 
-        Err(ErrorInternalServerError(
-            serde_json::to_string(&json_response).unwrap(),
-        ))
+    pub(crate) fn conflict<I: Into<String>>(self, msg: I) -> Result<Json<JsonResponse<T>>, Error> {
+        Err(ErrorConflict(self.set_msg(msg).to_string()))
     }
 }
 
@@ -121,17 +98,4 @@ where
     pub fn build() -> JsonResponseBuilder<T> {
         JsonResponseBuilder::default()
     }
-
-    pub(crate) fn new(message: String,
-                      id: Option<i32>,
-                      item:Option<T>,
-                      list: Option<Vec<T>>) -> Self {
-        tracing::debug!("Executed..");
-        JsonResponse {
-            message,
-            id,
-            item,
-            list,
-        }
-    }
 }
diff --git a/src/routes/client/add.rs b/src/routes/client/add.rs
index eee2ce07..292fc311 100644
--- a/src/routes/client/add.rs
+++ b/src/routes/client/add.rs
@@ -41,12 +41,12 @@ pub async fn add_handler(
                     client_count
                 );
 
-                return JsonResponse::build().err("Too many clients created".to_owned());
+                return JsonResponse::build().bad_request("Too many clients created");
             }
         }
         Err(e) => {
             tracing::error!("Failed to execute query: {:?}", e);
-            return JsonResponse::build().internal_error("Internal Server Error".to_owned());
+            return JsonResponse::build().internal_server_error("Internal Server Error");
         }
     };
 
@@ -79,12 +79,12 @@ pub async fn add_handler(
             return JsonResponse::build()
                 .set_id(client.id)
                 .set_item(Some(client))
-                .ok("OK".to_owned());
+                .ok("OK");
         }
         Err(e) => {
             tracing::error!("Failed to execute query: {:?}", e);
             let err = format!("Failed to insert. {}", e);
-            return JsonResponse::build().err(err);
+            return JsonResponse::build().bad_request(err);
         }
     }
 }
diff --git a/src/routes/client/disable.rs b/src/routes/client/disable.rs
index 383130b9..3ec7154c 100644
--- a/src/routes/client/disable.rs
+++ b/src/routes/client/disable.rs
@@ -62,7 +62,7 @@ pub async fn disable_handler(
         }
         Err(e) => {
             tracing::error!("Failed to execute query: {:?}", e);
-            JsonResponse::build().err("")
+            JsonResponse::build().bad_request("")
         }
     }
 }
diff --git a/src/routes/client/enable.rs b/src/routes/client/enable.rs
index a870c655..c6c53c7b 100644
--- a/src/routes/client/enable.rs
+++ b/src/routes/client/enable.rs
@@ -69,7 +69,7 @@ pub async fn enable_handler(
         }
         Err(e) => {
             tracing::error!("Failed to execute query: {:?}", e);
-            JsonResponse::build().err_internal_server_error("")
+            JsonResponse::build().internal_server_error("Internal error")
         }
     }
 }
diff --git a/src/routes/client/update.rs b/src/routes/client/update.rs
index f50b4ed7..c833508d 100644
--- a/src/routes/client/update.rs
+++ b/src/routes/client/update.rs
@@ -68,7 +68,7 @@ pub async fn update_handler(
         }
         Err(e) => {
             tracing::error!("Failed to execute query: {:?}", e);
-            JsonResponse::build().err_internal_server_error("")
+            JsonResponse::build().internal_server_error("")
         }
     }
 }
diff --git a/src/routes/rating/add.rs b/src/routes/rating/add.rs
index dd13eeeb..773fceeb 100644
--- a/src/routes/rating/add.rs
+++ b/src/routes/rating/add.rs
@@ -40,7 +40,7 @@ pub async fn add_handler(
         Err(e) => {
             tracing::error!("Failed to fetch product: {:?}, error: {:?}", form.obj_id, e);
             return JsonResponse::<models::Rating>::build()
-                .err(format!("Object not found {}", form.obj_id));
+                .not_found(format!("Object not found {}", form.obj_id).as_str());
         }
     };
 
@@ -63,12 +63,12 @@ pub async fn add_handler(
                 form.obj_id,
                 form.category
             );
-            return JsonResponse::build().conflict("Already rated".to_owned());
+            return JsonResponse::build().conflict("Already rated");
         }
         Err(sqlx::Error::RowNotFound) => {}
         Err(e) => {
             tracing::error!("Failed to fetch rating, error: {:?}", e);
-            return JsonResponse::build().err(format!("Internal Server Error"));
+            return JsonResponse::build().bad_request("Internal Server Error");
         }
     }
 
@@ -102,7 +102,7 @@ pub async fn add_handler(
         }
         Err(e) => {
             tracing::error!("Failed to execute query: {:?}", e);
-            JsonResponse::build().internal_error("Failed to insert".to_owned())
+            JsonResponse::build().internal_server_error("Failed to insert")
         }
     }
 }
diff --git a/src/routes/rating/get.rs b/src/routes/rating/get.rs
index 75281c0e..fcde66cc 100644
--- a/src/routes/rating/get.rs
+++ b/src/routes/rating/get.rs
@@ -32,11 +32,11 @@ pub async fn get_handler(
             return JsonResponse::build().set_item(Some(rating)).ok("OK");
         }
         Err(sqlx::Error::RowNotFound) => {
-            return JsonResponse::build().err("Not Found");
+            return JsonResponse::build().bad_request("Not Found");
         }
         Err(e) => {
             tracing::error!("Failed to fetch rating, error: {:?}", e);
-            return JsonResponse::build().err("Internal Server Error");
+            return JsonResponse::build().bad_request("Internal Server Error");
         }
     }
 }
@@ -58,14 +58,14 @@ pub async fn list_handler(
     {
         Ok(rating) => {
             tracing::info!("Ratings found: {:?}", rating.len());
-            return JsonResponse::build().set_list(rating).ok("OK".to_owned());
+            return JsonResponse::build().set_list(rating).ok("OK");
         }
         Err(sqlx::Error::RowNotFound) => {
-            return JsonResponse::build().not_found("Not Found".to_owned());
+            return JsonResponse::build().not_found("Not Found");
         }
         Err(e) => {
             tracing::error!("Failed to fetch rating, error: {:?}", e);
-            return JsonResponse::build().internal_error("Internal Server Error".to_owned());
+            return JsonResponse::build().internal_server_error("Internal Server Error");
         }
     }
 }
diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs
index b793925d..837eded8 100644
--- a/src/routes/stack/add.rs
+++ b/src/routes/stack/add.rs
@@ -34,7 +34,7 @@ pub async fn add(
         }
         Err(_err) => {
             let msg = format!("Invalid data. {:?}", _err);
-            return JsonResponse::<StackForm>::build().err(msg);
+            return JsonResponse::<StackForm>::build().bad_request(msg);
         }
     };
 
@@ -59,7 +59,7 @@ pub async fn add(
         Err(sqlx::Error::RowNotFound) => {}
         Err(e) => {
             tracing::error!("Failed to fetch stack info, error: {:?}", e);
-            return JsonResponse::build().err(format!("Internal Server Error"));
+            return JsonResponse::build().bad_request(format!("Internal Server Error"));
         }
     };
 
@@ -87,7 +87,7 @@ pub async fn add(
         let errors = form.validate().unwrap_err();
         let err_msg = format!("Invalid data received {:?}", &errors.to_string());
         tracing::debug!(err_msg);
-        return JsonResponse::build().err(errors.to_string());// tmp solution
+        return JsonResponse::build().bad_request(errors.to_string());// tmp solution
     }
 
     let body: Value = match serde_json::to_value::<StackForm>(form) {
@@ -120,11 +120,11 @@ pub async fn add(
                 "req_id: {} New stack details have been saved to database",
                 request_id
             );
-            return JsonResponse::build().set_id(record.id).ok("OK".to_owned());
+            return JsonResponse::build().set_id(record.id).ok("OK");
         }
         Err(e) => {
             tracing::error!("req_id: {} Failed to execute query: {:?}", request_id, e);
-            return JsonResponse::build().err("Internal Server Error".to_owned());
+            return JsonResponse::build().internal_server_error("Internal Server Error");
         }
     };
 }
diff --git a/src/routes/stack/compose.rs b/src/routes/stack/compose.rs
index 4dc3c7f1..321f88bb 100644
--- a/src/routes/stack/compose.rs
+++ b/src/routes/stack/compose.rs
@@ -58,10 +58,10 @@ pub async fn add(
             return JsonResponse::build()
                 .set_id(id)
                 .set_item(fc.unwrap())
-                .ok("Success".to_owned());
+                .ok("Success");
         }
         None => {
-            return JsonResponse::build().err("Could not generate compose file".to_owned());
+            return JsonResponse::build().bad_request("Could not generate compose file");
         }
     }
 }
@@ -118,11 +118,11 @@ pub async fn admin(
             // tracing::debug!("Docker compose file content {:?}", fc);
             return JsonResponse::build()
                 .set_id(id)
-                .set_item(fc).ok("Success".to_owned());
+                .set_item(fc).ok("Success");
 
         }
         None => {
-            return JsonResponse::build().err("Could not generate compose file".to_owned());
+            return JsonResponse::build().bad_request("Could not generate compose file");
         }
     }
 }
diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs
index 645a14a2..b0f512c3 100644
--- a/src/routes/stack/deploy.rs
+++ b/src/routes/stack/deploy.rs
@@ -96,10 +96,10 @@ pub async fn add(
 
             assert_eq!(confirm, Confirmation::NotRequested);
             tracing::debug!("Message sent to rabbitmq");
-            return JsonResponse::<Stack>::build().set_id(id).ok("Success".to_owned());
+            return JsonResponse::<Stack>::build().set_id(id).ok("Success");
         }
         None => {
-            JsonResponse::build().internal_error("Deployment failed".to_owned())
+            JsonResponse::build().internal_server_error("Deployment failed")
         }
     }
 }
diff --git a/src/routes/stack/get.rs b/src/routes/stack/get.rs
index 271d2dcb..61aa2156 100644
--- a/src/routes/stack/get.rs
+++ b/src/routes/stack/get.rs
@@ -31,14 +31,14 @@ pub async fn item(
     {
         Ok(stack) => {
             tracing::info!("Stack found: {:?}", stack.id,);
-            return JsonResponse::build().set_item(Some(stack)).ok("OK".to_owned());
+            return JsonResponse::build().set_item(Some(stack)).ok("OK");
         }
         Err(sqlx::Error::RowNotFound) => {
-            JsonResponse::build().not_found("Record not found".to_owned())
+            JsonResponse::build().not_found("Record not found")
         }
         Err(e) => {
             tracing::error!("Failed to fetch stack, error: {:?}", e);
-            return JsonResponse::build().internal_error("Could not fetch data".to_owned());
+            return JsonResponse::build().internal_server_error("Could not fetch data");
         }
     }
 }
@@ -81,7 +81,7 @@ pub async fn list(
         }
         Err(e) => {
             tracing::error!("Failed to fetch stack, error: {:?}", e);
-            return JsonResponse::build().internal_error("Could not fetch".to_string());
+            return JsonResponse::build().internal_server_error("Could not fetch".to_string());
         }
     }
 }
diff --git a/src/routes/stack/update.rs b/src/routes/stack/update.rs
index 56514ebe..98280175 100644
--- a/src/routes/stack/update.rs
+++ b/src/routes/stack/update.rs
@@ -43,7 +43,7 @@ pub async fn update(
         Err(e) => {
             tracing::error!("Failed to fetch record: {:?}, error: {:?}", id, e);
             return JsonResponse::<models::Stack>::build()
-                .err(format!("Object not found {}", id));
+                .not_found(format!("Object not found {}", id));
         }
     };
 
@@ -71,7 +71,7 @@ pub async fn update(
         let errors = form.validate().unwrap_err();
         let err_msg = format!("Invalid data received {:?}", &errors.to_string());
         tracing::debug!(err_msg);
-        return JsonResponse::build().err(errors.to_string());
+        return JsonResponse::build().bad_request(errors.to_string());
     }
 
     let body: Value = match serde_json::to_value::<StackForm>(form.into_inner()) {
@@ -105,11 +105,11 @@ pub async fn update(
                 "req_id: {} stack details have been saved to database",
                 request_id
             );
-            return JsonResponse::build().set_id(id).ok("OK".to_owned());
+            return JsonResponse::build().set_id(id).ok("OK");
         }
         Err(e) => {
             tracing::error!("req_id: {} Failed to execute query: {:?}", request_id, e);
-            return JsonResponse::build().err("Internal Server Error".to_owned());
+            return JsonResponse::build().bad_request("Internal Server Error");
         }
     };
 }
diff --git a/tests/model_user_stack.rs b/tests/model_user_stack.rs
index be93cec0..e8ee9b6f 100644
--- a/tests/model_user_stack.rs
+++ b/tests/model_user_stack.rs
@@ -18,7 +18,7 @@ fn test_deserialize_user_stack_web() {
     //     }
     //     Err(_err) => {
     //         let msg = format!("Invalid data. {:?}", _err);
-    //         return JsonResponse::<StackForm>::build().err(msg);
+    //         return JsonResponse::<StackForm>::build().bad_request(msg);
     //     }
     // };
     //
@@ -38,7 +38,7 @@ fn test_deserialize_user_stack() {
     //     }
     //     Err(_err) => {
     //         let msg = format!("Invalid data. {:?}", _err);
-    //         return JsonResponse::<StackForm>::build().err(msg);
+    //         return JsonResponse::<StackForm>::build().bad_request(msg);
     //     }
     // };
     //

From c11fec45f31fbe83509c44686a4871e7d1d00c5e Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Tue, 5 Dec 2023 18:00:58 +0200
Subject: [PATCH 038/284] issue-auth working on optimisations

---
 src/middleware/trydirect.rs | 68 +++++++++++++------------------------
 1 file changed, 23 insertions(+), 45 deletions(-)

diff --git a/src/middleware/trydirect.rs b/src/middleware/trydirect.rs
index cf5079b2..c270fba9 100644
--- a/src/middleware/trydirect.rs
+++ b/src/middleware/trydirect.rs
@@ -13,12 +13,9 @@ use crate::helpers::JsonResponse;
 use crate::models::user::User; //todo
 
 #[tracing::instrument(name = "Trydirect bearer guard.")]
-pub async fn bearer_guard(
-    req: ServiceRequest,
-    credentials: BearerAuth,
-    ) -> Result<ServiceRequest, (Error, ServiceRequest)> {
-    async {
-        let settings = req.app_data::<web::Data<Settings>>().unwrap();
+pub async fn bearer_guard( req: ServiceRequest, credentials: BearerAuth) -> Result<ServiceRequest, (Error, ServiceRequest)> {
+    let settings = req.app_data::<web::Data<Settings>>().unwrap();
+    async move {
         let client = reqwest::Client::new();
         let resp = client
             .get(&settings.auth_url)
@@ -26,51 +23,32 @@ pub async fn bearer_guard(
             .header(CONTENT_TYPE, "application/json")
             .header(ACCEPT, "application/json")
             .send()
-            .await;
+            .await
+            .map_err(|err| "no resp from auth server".to_string())?;
 
-        let resp = match resp {
-            Ok(resp) if resp.status().is_success() => resp,
-            Ok(resp) => {
-                tracing::error!("Authentication service returned no success {:?}", resp);
-                return Err(("401 Unauthorized".to_string(), req));
-            }
-            Err(err) => {
-                tracing::error!("error from reqwest {:?}", err);
-                return Err((err.to_string(), req));
-            }
-        };
+        if !resp.status().is_success() {
+            return Err(("401 Unauthorized".to_string(), req));
+        }
 
-        let user_form: UserForm = match resp.json().await {
+        resp
+            .json()
+            .await
+            .map_err(|err| "can't parse the response body".to_string())?
+            .try_into() //User"Could not create User from form data"
+    }.await
+    .map(move |result| {
+        match result {
             Ok(user) => {
-                tracing::info!("unpacked user {user:?}");
-                user
-            }
-            Err(err) => {
-                tracing::error!("can't parse the response body {:?}", err);
-                return Err(("can't parse the response body".to_string(), req));
-            }
-        };
+                let existent_user = req.extensions_mut().insert(user);
+                if existent_user.is_some() {
+                    return Err((ErrorUnauthorized(JsonResponse::<i32>::build().set_msg("user already logged").to_string()), req));
+                }
 
-        let user: User = match user_form.try_into() {
-            Ok(user) => user,
+                return Ok(req);
+            }
             Err(err) => {
-                tracing::error!("Could not create User from form data: {:?}", err);
-                return Err(("Could not create User from form data".to_string(), req));
+                return Err((ErrorUnauthorized(JsonResponse::<i32>::build().set_msg(err).to_string()), req));
             }
-        };
-        let existent_user = req.extensions_mut().insert(user);
-        if existent_user.is_some() {
-            tracing::error!("already logged {existent_user:?}");
-            return Err(("user already logged".to_string(), req));
         }
-
-        Ok(req)
-    }.await
-    .map_err(|(err, req)| {
-        tracing::error!("Authentication service returned no success {:?}", err);
-        (ErrorUnauthorized(JsonResponse::<i32>::build().set_msg(err).to_string()), req) //todo
-                                                                                        //default
-                                                                                        //type for
-                                                                                        //JsonResponse
     })
 }

From 29f009515f3094cc2812ed28beab36dc55c36564 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Tue, 5 Dec 2023 18:04:10 +0200
Subject: [PATCH 039/284] issue-auth userform

---
 src/middleware/trydirect.rs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/middleware/trydirect.rs b/src/middleware/trydirect.rs
index c270fba9..05c1aca4 100644
--- a/src/middleware/trydirect.rs
+++ b/src/middleware/trydirect.rs
@@ -27,11 +27,11 @@ pub async fn bearer_guard( req: ServiceRequest, credentials: BearerAuth) -> Resu
             .map_err(|err| "no resp from auth server".to_string())?;
 
         if !resp.status().is_success() {
-            return Err(("401 Unauthorized".to_string(), req));
+            return Err("401 Unauthorized".to_string());
         }
 
         resp
-            .json()
+            .json::<UserForm>()
             .await
             .map_err(|err| "can't parse the response body".to_string())?
             .try_into() //User"Could not create User from form data"

From 61e6dd0b9dcfaf93900e89eef55996f2a137075f Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Wed, 6 Dec 2023 08:32:44 +0200
Subject: [PATCH 040/284] issue-auth trydirect middleware

---
 src/middleware/trydirect.rs | 64 ++++++++++++++++++-------------------
 1 file changed, 31 insertions(+), 33 deletions(-)

diff --git a/src/middleware/trydirect.rs b/src/middleware/trydirect.rs
index 05c1aca4..bc8fa065 100644
--- a/src/middleware/trydirect.rs
+++ b/src/middleware/trydirect.rs
@@ -15,40 +15,38 @@ use crate::models::user::User; //todo
 #[tracing::instrument(name = "Trydirect bearer guard.")]
 pub async fn bearer_guard( req: ServiceRequest, credentials: BearerAuth) -> Result<ServiceRequest, (Error, ServiceRequest)> {
     let settings = req.app_data::<web::Data<Settings>>().unwrap();
-    async move {
-        let client = reqwest::Client::new();
-        let resp = client
-            .get(&settings.auth_url)
-            .bearer_auth(credentials.token())
-            .header(CONTENT_TYPE, "application/json")
-            .header(ACCEPT, "application/json")
-            .send()
-            .await
-            .map_err(|err| "no resp from auth server".to_string())?;
+    let token = credentials.token();
+    let user = fetch_user(settings.auth_url.as_str(), token).await;
+    if let Err(err) = user {
+        return Err((ErrorUnauthorized(JsonResponse::<i32>::build().set_msg(err).to_string()), req));
+    }
 
-        if !resp.status().is_success() {
-            return Err("401 Unauthorized".to_string());
-        }
+    let user = user.unwrap();
+    if req.extensions_mut().insert(user).is_some() {
+        return Err((ErrorUnauthorized(JsonResponse::<i32>::build().set_msg("user already logged").to_string()), req));
+    }
 
-        resp
-            .json::<UserForm>()
-            .await
-            .map_err(|err| "can't parse the response body".to_string())?
-            .try_into() //User"Could not create User from form data"
-    }.await
-    .map(move |result| {
-        match result {
-            Ok(user) => {
-                let existent_user = req.extensions_mut().insert(user);
-                if existent_user.is_some() {
-                    return Err((ErrorUnauthorized(JsonResponse::<i32>::build().set_msg("user already logged").to_string()), req));
-                }
+    Ok(req)
+}
+
+async fn fetch_user(auth_url: &str, token: &str) -> Result<User, String> {
+    let client = reqwest::Client::new();
+    let resp = client
+        .get(auth_url)
+        .bearer_auth(token)
+        .header(CONTENT_TYPE, "application/json")
+        .header(ACCEPT, "application/json")
+        .send()
+        .await
+        .map_err(|err| "no resp from auth server".to_string())?;
+
+    if !resp.status().is_success() {
+        return Err("401 Unauthorized".to_string());
+    }
 
-                return Ok(req);
-            }
-            Err(err) => {
-                return Err((ErrorUnauthorized(JsonResponse::<i32>::build().set_msg(err).to_string()), req));
-            }
-        }
-    })
+    resp
+        .json::<UserForm>()
+        .await
+        .map_err(|err| "can't parse the response body".to_string())?
+        .try_into()
 }

From 7ea0f30bff0d773243aa3c64a76e405a18251d5e Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Thu, 7 Dec 2023 07:05:02 +0200
Subject: [PATCH 041/284] issue-auth optimized use block in trydirect.rs

---
 src/middleware/trydirect.rs | 14 +++-----------
 1 file changed, 3 insertions(+), 11 deletions(-)

diff --git a/src/middleware/trydirect.rs b/src/middleware/trydirect.rs
index bc8fa065..3e1db806 100644
--- a/src/middleware/trydirect.rs
+++ b/src/middleware/trydirect.rs
@@ -1,16 +1,8 @@
-use crate::configuration::Settings;
-use futures::future::{FutureExt};
-use crate::forms::user::UserForm;
-use actix_web::dev::ServiceRequest;
-use actix_web::error::{ErrorInternalServerError, ErrorUnauthorized};
-use actix_web::web::{self};
-use actix_web::Error;
-use actix_web::HttpMessage;
+use crate::{models::user::User, configuration::Settings, forms::user::UserForm, helpers::JsonResponse};
+use actix_web::{web, dev::ServiceRequest, Error, HttpMessage, error::{ErrorInternalServerError, ErrorUnauthorized}};
 use actix_web_httpauth::extractors::bearer::BearerAuth;
+use futures::future::{FutureExt};
 use reqwest::header::{ACCEPT, CONTENT_TYPE};
-use crate::helpers::JsonResponse;
-
-use crate::models::user::User; //todo
 
 #[tracing::instrument(name = "Trydirect bearer guard.")]
 pub async fn bearer_guard( req: ServiceRequest, credentials: BearerAuth) -> Result<ServiceRequest, (Error, ServiceRequest)> {

From e3b9efd7a62259b35c581e07a97dfc92be0bafde Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Thu, 7 Dec 2023 07:08:53 +0200
Subject: [PATCH 042/284] issue-auth wiremock --dev

---
 Cargo.lock | 193 +++++++++++++++++++++++++++++++++++++++++++++++++----
 Cargo.toml |   1 +
 2 files changed, 181 insertions(+), 13 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index d4cbf7cc..cdd9ce8c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -64,7 +64,7 @@ dependencies = [
  "mime",
  "percent-encoding",
  "pin-project-lite",
- "rand",
+ "rand 0.8.5",
  "sha1",
  "smallvec",
  "tokio",
@@ -232,7 +232,7 @@ version = "0.7.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
 dependencies = [
- "getrandom",
+ "getrandom 0.2.10",
  "once_cell",
  "version_check",
 ]
@@ -244,7 +244,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
 dependencies = [
  "cfg-if",
- "getrandom",
+ "getrandom 0.2.10",
  "once_cell",
  "version_check",
 ]
@@ -390,6 +390,22 @@ dependencies = [
  "windows-sys",
 ]
 
+[[package]]
+name = "anyhow"
+version = "1.0.75"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
+
+[[package]]
+name = "assert-json-diff"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
 [[package]]
 name = "async-channel"
 version = "1.9.0"
@@ -902,6 +918,25 @@ dependencies = [
  "syn 1.0.109",
 ]
 
+[[package]]
+name = "deadpool"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "421fe0f90f2ab22016f32a9881be5134fdd71c65298917084b0c7477cbc3856e"
+dependencies = [
+ "async-trait",
+ "deadpool-runtime",
+ "num_cpus",
+ "retain_mut",
+ "tokio",
+]
+
+[[package]]
+name = "deadpool-runtime"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63dfa964fe2a66f3fde91fc70b267fe193d822c7e603e2a675a49a7f46ad3f49"
+
 [[package]]
 name = "deranged"
 version = "0.3.9"
@@ -1232,6 +1267,12 @@ version = "0.3.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2"
 
+[[package]]
+name = "futures-timer"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
+
 [[package]]
 name = "futures-util"
 version = "0.3.29"
@@ -1270,6 +1311,17 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "getrandom"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.9.0+wasi-snapshot-preview1",
+]
+
 [[package]]
 name = "getrandom"
 version = "0.2.10"
@@ -1401,6 +1453,27 @@ dependencies = [
  "pin-project-lite",
 ]
 
+[[package]]
+name = "http-types"
+version = "2.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e9b187a72d63adbfba487f48095306ac823049cb504ee195541e91c7775f5ad"
+dependencies = [
+ "anyhow",
+ "async-channel",
+ "base64 0.13.1",
+ "futures-lite",
+ "http",
+ "infer",
+ "pin-project-lite",
+ "rand 0.7.3",
+ "serde",
+ "serde_json",
+ "serde_qs",
+ "serde_urlencoded",
+ "url",
+]
+
 [[package]]
 name = "httparse"
 version = "1.8.0"
@@ -1511,6 +1584,12 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "infer"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac"
+
 [[package]]
 name = "inout"
 version = "0.1.3"
@@ -1891,7 +1970,7 @@ dependencies = [
  "cbc",
  "cipher",
  "des",
- "getrandom",
+ "getrandom 0.2.10",
  "hmac",
  "lazy_static",
  "rc2",
@@ -2147,6 +2226,19 @@ dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "rand"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
+dependencies = [
+ "getrandom 0.1.16",
+ "libc",
+ "rand_chacha 0.2.2",
+ "rand_core 0.5.1",
+ "rand_hc",
+]
+
 [[package]]
 name = "rand"
 version = "0.8.5"
@@ -2154,8 +2246,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
 dependencies = [
  "libc",
- "rand_chacha",
- "rand_core",
+ "rand_chacha 0.3.1",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.5.1",
 ]
 
 [[package]]
@@ -2165,7 +2267,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
 dependencies = [
  "ppv-lite86",
- "rand_core",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+dependencies = [
+ "getrandom 0.1.16",
 ]
 
 [[package]]
@@ -2174,7 +2285,16 @@ version = "0.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
 dependencies = [
- "getrandom",
+ "getrandom 0.2.10",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+dependencies = [
+ "rand_core 0.5.1",
 ]
 
 [[package]]
@@ -2230,7 +2350,7 @@ version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
 dependencies = [
- "getrandom",
+ "getrandom 0.2.10",
  "redox_syscall 0.2.16",
  "thiserror",
 ]
@@ -2317,6 +2437,12 @@ dependencies = [
  "winreg",
 ]
 
+[[package]]
+name = "retain_mut"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0"
+
 [[package]]
 name = "ring"
 version = "0.16.20"
@@ -2339,7 +2465,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fce3045ffa7c981a6ee93f640b538952e155f1ae3a1a02b84547fc7a56b7059a"
 dependencies = [
  "cc",
- "getrandom",
+ "getrandom 0.2.10",
  "libc",
  "spin 0.9.8",
  "untrusted 0.9.0",
@@ -2567,6 +2693,17 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "serde_qs"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7715380eec75f029a4ef7de39a9200e0a63823176b759d055b613f5a87df6a6"
+dependencies = [
+ "percent-encoding",
+ "serde",
+ "thiserror",
+]
+
 [[package]]
 name = "serde_urlencoded"
 version = "0.7.1"
@@ -2783,7 +2920,7 @@ dependencies = [
  "once_cell",
  "paste",
  "percent-encoding",
- "rand",
+ "rand 0.8.5",
  "rustls 0.20.9",
  "rustls-pemfile",
  "serde",
@@ -2854,7 +2991,7 @@ dependencies = [
  "hmac",
  "indexmap 2.1.0",
  "lapin",
- "rand",
+ "rand 0.8.5",
  "regex",
  "reqwest",
  "serde",
@@ -2873,6 +3010,7 @@ dependencies = [
  "tracing-log",
  "tracing-subscriber",
  "uuid",
+ "wiremock",
 ]
 
 [[package]]
@@ -3312,6 +3450,7 @@ dependencies = [
  "form_urlencoded",
  "idna",
  "percent-encoding",
+ "serde",
 ]
 
 [[package]]
@@ -3326,7 +3465,7 @@ version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc"
 dependencies = [
- "getrandom",
+ "getrandom 0.2.10",
  "serde",
 ]
 
@@ -3363,6 +3502,12 @@ dependencies = [
  "try-lock",
 ]
 
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
 [[package]]
 name = "wasi"
 version = "0.10.0+wasi-snapshot-preview1"
@@ -3587,6 +3732,28 @@ dependencies = [
  "windows-sys",
 ]
 
+[[package]]
+name = "wiremock"
+version = "0.5.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13a3a53eaf34f390dd30d7b1b078287dd05df2aa2e21a589ccb80f5c7253c2e9"
+dependencies = [
+ "assert-json-diff",
+ "async-trait",
+ "base64 0.21.4",
+ "deadpool",
+ "futures",
+ "futures-timer",
+ "http-types",
+ "hyper",
+ "log",
+ "once_cell",
+ "regex",
+ "serde",
+ "serde_json",
+ "tokio",
+]
+
 [[package]]
 name = "yaml-rust"
 version = "0.4.5"
diff --git a/Cargo.toml b/Cargo.toml
index f3d2357d..b23bf3b2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -71,3 +71,4 @@ indexmap = ["dep:indexmap"]
 
 [dev-dependencies]
 glob = "0.3"
+wiremock = "0.5.22"

From 7b4df7114f25743392bf526ab31499827fbd1f62 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Thu, 7 Dec 2023 07:34:09 +0200
Subject: [PATCH 043/284] issue-auth tests/middleware_trydirect.rs

---
 tests/common/mod.rs           | 36 +++++++++++++++++++----------------
 tests/middleware_trydirect.rs | 29 ++++++++++++++++++++++++++++
 2 files changed, 49 insertions(+), 16 deletions(-)
 create mode 100644 tests/middleware_trydirect.rs

diff --git a/tests/common/mod.rs b/tests/common/mod.rs
index afc41343..e5804ab3 100644
--- a/tests/common/mod.rs
+++ b/tests/common/mod.rs
@@ -1,24 +1,10 @@
 use actix_web::{get, web, App, HttpServer, Responder};
 use sqlx::{Connection, Executor, PgConnection, PgPool};
-use stacker::configuration::{get_configuration, DatabaseSettings};
+use stacker::configuration::{get_configuration, DatabaseSettings, Settings};
 use stacker::forms;
 use std::net::TcpListener;
 
-pub async fn spawn_app() -> TestApp {
-    let mut configuration = get_configuration().expect("Failed to get configuration");
-
-    let listener = std::net::TcpListener::bind("127.0.0.1:0")
-        .expect("Failed to bind port for testing auth server");
-
-    configuration.auth_url = format!(
-        "http://127.0.0.1:{}/me",
-        listener.local_addr().unwrap().port()
-    );
-    println!("Auth Server is running on: {}", configuration.auth_url);
-
-    let handle = tokio::spawn(mock_auth_server(listener));
-    handle.await.expect("Auth Server can not be started");
-
+pub async fn spawn_app_with_configuration(mut configuration: Settings) -> TestApp {
     let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("Failed to bind random port");
 
     let port = listener.local_addr().unwrap().port();
@@ -40,6 +26,24 @@ pub async fn spawn_app() -> TestApp {
     }
 }
 
+pub async fn spawn_app() -> TestApp {
+    let mut configuration = get_configuration().expect("Failed to get configuration");
+
+    let listener = std::net::TcpListener::bind("127.0.0.1:0")
+        .expect("Failed to bind port for testing auth server");
+
+    configuration.auth_url = format!(
+        "http://127.0.0.1:{}/me",
+        listener.local_addr().unwrap().port()
+    );
+    println!("Auth Server is running on: {}", configuration.auth_url);
+
+    let handle = tokio::spawn(mock_auth_server(listener));
+    handle.await.expect("Auth Server can not be started");
+
+    spawn_app_with_configuration(configuration).await
+}
+
 pub async fn configure_database(config: &DatabaseSettings) -> PgPool {
     let mut connection = PgConnection::connect(&config.connection_string_without_db())
         .await
diff --git a/tests/middleware_trydirect.rs b/tests/middleware_trydirect.rs
new file mode 100644
index 00000000..05906181
--- /dev/null
+++ b/tests/middleware_trydirect.rs
@@ -0,0 +1,29 @@
+mod common;
+use wiremock::MockServer;
+
+#[tokio::test]
+async fn middleware_trydirect_works() {
+    // 1. Arrange
+    let trydirect_auth_server = MockServer::start().await;
+
+    // 2. Act
+    // 3. Assert
+
+    println!("Before spawn_app");
+    let app = common::spawn_app().await; // server
+    println!("After spawn_app");
+    let client = reqwest::Client::new(); // client
+
+    let response = client
+        .get(&format!("{}/health_check", &app.address))
+        .send()
+        .await
+        .expect("Failed to execute request.");
+
+    assert!(response.status().is_success());
+    assert_eq!(Some(0), response.content_length());
+
+
+    //todo header stacker-id not found
+    //
+}

From d86e37ff53dbede0902d22b84eda6775ebae26ea Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Fri, 8 Dec 2023 16:54:42 +0200
Subject: [PATCH 044/284] issue-auth - db_fetch_client

---
 src/middleware/client.rs | 56 +++++++++++++++++++++-------------------
 1 file changed, 29 insertions(+), 27 deletions(-)

diff --git a/src/middleware/client.rs b/src/middleware/client.rs
index 4309ac51..a047b139 100644
--- a/src/middleware/client.rs
+++ b/src/middleware/client.rs
@@ -77,31 +77,11 @@ where
             let client_id: i32 = get_header(&req, "stacker-id")?;
             let hash: String = get_header(&req, "stacker-hash")?;
 
-            let query_span = tracing::info_span!("Fetching the client by ID");
-            let db_pool = req.app_data::<web::Data<Pool<Postgres>>>().unwrap();
-
-            let mut client: Client = match sqlx::query_as!(
-                Client,
-                r#"
-            SELECT
-               id, user_id, secret
-            FROM client c
-            WHERE c.id = $1
-            "#,
-                client_id,
-            )
-            .fetch_one(db_pool.get_ref())
-            .instrument(query_span)
-            .await
-            {
-                Ok(client) if client.secret.is_some() => Ok(client),
-                Ok(_client) => Err("client is not active".to_string()),
-                Err(sqlx::Error::RowNotFound) => Err("the client is not found".to_string()),
-                Err(e) => {
-                    tracing::error!("Failed to execute fetch query: {:?}", e);
-                    Err("".to_string())
-                }
-            }?;
+            let db_pool = req.app_data::<web::Data<Pool<Postgres>>>().unwrap().get_ref();
+            let mut client: Client = db_fetch_client(db_pool, client_id).await?;
+            if client.secret.is_none() {
+                return Err("client is not active".to_string());
+            }
 
             let content_length: usize = get_header(&req, CONTENT_LENGTH.as_str())?;
             let mut bytes = BytesMut::with_capacity(content_length);
@@ -165,9 +145,31 @@ where
 
     let header_value: &str = header_value
         .to_str()
-        .map_err(|_| format!("header {header_name} can't be converted to string"))?; //map_err
-                                                                                     //
+        .map_err(|_| format!("header {header_name} can't be converted to string"))?; 
+
     header_value
         .parse::<T>()
         .map_err(|_| format!("header {header_name} has wrong type"))
 }
+
+async fn db_fetch_client(db_pool: &Pool<Postgres>, client_id: i32) -> Result<Client, String> {
+    let query_span = tracing::info_span!("Fetching the client by ID");
+
+    sqlx::query_as!(
+        Client,
+        r#"SELECT id, user_id, secret FROM client c WHERE c.id = $1"#,
+        client_id,
+        )
+        .fetch_one(db_pool)
+        .instrument(query_span)
+        .await
+        .map_err(|err| {
+            match err {
+                sqlx::Error::RowNotFound => "the client is not found".to_string(),
+                e => {
+                    tracing::error!("Failed to execute fetch query: {:?}", e);
+                    String::new()
+                }
+            }
+        })
+}

From 3f9f6d0142aaa68885186aff5953551795ed9232 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Fri, 8 Dec 2023 17:26:25 +0200
Subject: [PATCH 045/284] issue-auth - optimize src/middleware/client.rs

---
 src/middleware/client.rs | 53 ++++++++++++++++++++++------------------
 1 file changed, 29 insertions(+), 24 deletions(-)

diff --git a/src/middleware/client.rs b/src/middleware/client.rs
index a047b139..27aa97b7 100644
--- a/src/middleware/client.rs
+++ b/src/middleware/client.rs
@@ -75,7 +75,7 @@ where
         let service = self.service.clone();
         async move {
             let client_id: i32 = get_header(&req, "stacker-id")?;
-            let hash: String = get_header(&req, "stacker-hash")?;
+            let header_hash: String = get_header(&req, "stacker-hash")?;
 
             let db_pool = req.app_data::<web::Data<Pool<Postgres>>>().unwrap().get_ref();
             let mut client: Client = db_fetch_client(db_pool, client_id).await?;
@@ -83,32 +83,12 @@ where
                 return Err("client is not active".to_string());
             }
 
-            let content_length: usize = get_header(&req, CONTENT_LENGTH.as_str())?;
-            let mut bytes = BytesMut::with_capacity(content_length);
-            let mut payload = req.take_payload();
-            while let Some(chunk) = payload.next().await {
-                bytes.extend_from_slice(&chunk.expect("can't unwrap the chunk"));
-            }
-
-            let mut mac =
-                match Hmac::<Sha256>::new_from_slice(client.secret.as_ref().unwrap().as_bytes()) {
-                    Ok(mac) => mac,
-                    Err(err) => {
-                        tracing::error!("error generating hmac {err:?}");
-                        return Err("".to_string());
-                    }
-                };
-
-            mac.update(bytes.as_ref());
-            let computed_hash = format!("{:x}", mac.finalize().into_bytes());
-            if hash != computed_hash {
+            let client_secret = client.secret.as_ref().unwrap().as_bytes();
+            let body_hash = compute_body_hash(&mut req, client_secret).await?;
+            if header_hash != body_hash {
                 return Err("hash is wrong".to_string());
             }
 
-            let (_, mut payload) = actix_http::h1::Payload::create(true);
-            payload.unread_data(bytes.into());
-            req.set_payload(payload.into());
-
             match req.extensions_mut().insert(Arc::new(client)) {
                 Some(_) => {
                     tracing::error!("client middleware already called once");
@@ -173,3 +153,28 @@ async fn db_fetch_client(db_pool: &Pool<Postgres>, client_id: i32) -> Result<Cli
             }
         })
 }
+
+async fn compute_body_hash(req: &mut ServiceRequest, client_secret: &[u8]) -> Result<String, String> {
+    let content_length: usize = get_header(req, CONTENT_LENGTH.as_str())?;
+    let mut body = BytesMut::with_capacity(content_length);
+    let mut payload = req.take_payload();
+    while let Some(chunk) = payload.next().await {
+        body.extend_from_slice(&chunk.expect("can't unwrap the chunk"));
+    }
+
+    let mut mac =
+        match Hmac::<Sha256>::new_from_slice(client_secret) {
+            Ok(mac) => mac,
+            Err(err) => {
+                tracing::error!("error generating hmac {err:?}");
+                return Err("".to_string());
+            }
+        };
+
+    mac.update(body.as_ref());
+    let (_, mut payload) = actix_http::h1::Payload::create(true);
+    payload.unread_data(body.into());
+    req.set_payload(payload.into());
+
+    Ok(format!("{:x}", mac.finalize().into_bytes()))
+}

From 02de6cb344424276553bf8ded06f5a77c3fa5d34 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Fri, 8 Dec 2023 20:42:46 +0200
Subject: [PATCH 046/284] working non-optimized version of dc generator

---
 src/forms/stack.rs                 |  6 +--
 src/helpers/stack/builder.rs       | 66 ++++++++++++++++++++++++++----
 tests/custom-stack-payload-11.json |  2 +-
 3 files changed, 61 insertions(+), 13 deletions(-)

diff --git a/src/forms/stack.rs b/src/forms/stack.rs
index 544dc3df..f09b0528 100644
--- a/src/forms/stack.rs
+++ b/src/forms/stack.rs
@@ -264,12 +264,12 @@ pub struct App {
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct Environment {
-    environment: Option<Vec<HashMap<String, String>>>
+    pub(crate) environment: Option<Vec<HashMap<String, String>>>
 }
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct Volume {
-    host_path: Option<String>,
-    container_path: Option<String>
+    pub(crate) host_path: Option<String>,
+    pub(crate) container_path: Option<String>
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index 00262575..e718dd19 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -1,11 +1,6 @@
-use crate::helpers::stack::dctypes::{
-    Compose,
-    Port,
-    Ports,
-    PublishedPort,
-    Service,
-    Services
-};
+use std::collections::HashMap;
+use indexmap::IndexMap;
+use crate::helpers::stack::dctypes::{Compose, Port, Ports, PublishedPort, Service, Services, Volumes, Environment, Entrypoint, AdvancedVolumes, SingleValue};
 use serde_yaml;
 use crate::forms::{StackForm, stack};
 use crate::models::stack::Stack;
@@ -37,6 +32,24 @@ pub struct DcBuilder {
     pub(crate) stack: Stack
 }
 
+impl TryInto<AdvancedVolumes> for &stack::Volume {
+    type Error = String;
+    fn try_into(self) -> Result<AdvancedVolumes, Self::Error> {
+
+        let source = self.host_path.clone();
+        let target = self.container_path.clone();
+        tracing::debug!("Volume conversion result: source: {:?} target: {:?}", source, target);
+        Ok(AdvancedVolumes {
+            source: source,
+            target: target.unwrap_or("".to_string()),
+            _type: "".to_string(),
+            read_only: false,
+            bind: None,
+            volume: None,
+            tmpfs: None,
+        })
+    }
+}
 
 impl TryInto<Port> for &stack::Port {
     type Error = String;
@@ -79,6 +92,21 @@ fn convert_shared_ports(ports: Option<Vec<stack::Port>>) -> Result<Vec<Port>, St
     Ok(_ports)
 }
 
+// impl TryInto<IndexMap<String, Option<SingleValue>>> for HashMap<String, String> {
+//     type Error = String;
+//
+//     fn try_into(self) -> Result<IndexMap<String, Option<SingleValue>>, Self::Error> {
+//         let mut index_map = IndexMap::new();
+//
+//         for (key, value) in self {
+//             let single_value = Some(SingleValue::String(value));
+//             index_map.insert(key, single_value);
+//         }
+//
+//         Ok(index_map)
+//     }
+// }
+
 impl DcBuilder {
 
     pub fn new(stack: Stack) -> Self {
@@ -110,9 +138,28 @@ impl DcBuilder {
                         .iter()
                         .map(|x| x.try_into().unwrap())
                         .collect();
+
+                    let volumes: Vec<AdvancedVolumes> = app_type.app.volumes
+                        .unwrap()
+                        .iter()
+                        .map(|x| x.try_into().unwrap())
+                        .collect();
+
+                    let mut envs = IndexMap::new();
+                    for item in app_type.app.environment.environment.unwrap() {
+                        let items = item
+                            .into_iter()
+                            .map(|(k, v)| (k, Some(SingleValue::String(v.clone()))))
+                            .collect::<IndexMap<_, _>>();
+
+                        envs.extend(items);
+                    }
+                    // tracing::debug!("envs: {:?}", envs);
                     service.ports = Ports::Long(ports);
                     service.restart = Some("always".to_owned());
-                    tracing::debug!("service 1 {:?}", &service);
+                    service.volumes = Volumes::Advanced(volumes);
+                    service.environment = Environment::KvPair(envs);
+                    // tracing::debug!("service 1 {:?}", &service);
                     services.insert(
                         code,
                         Some(service),
@@ -183,6 +230,7 @@ impl DcBuilder {
             }
         }
 
+
         let compose_content = Compose {
             version: Some("3.8".to_string()),
             services: {
diff --git a/tests/custom-stack-payload-11.json b/tests/custom-stack-payload-11.json
index d87ce41e..ab0314d5 100644
--- a/tests/custom-stack-payload-11.json
+++ b/tests/custom-stack-payload-11.json
@@ -1 +1 @@
-{"commonDomain":"justates222t.com","domainList":{},"region":"fsn1","zone":null,"server":"cx21","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":["portainer_ce_feature"],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":true,"disk_type":"pd-standart","cloud_token":"**********","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"_etag":null,"_id":180,"_created":"2021-12-17T08:11:40.875486","_updated":"2023-11-21T17:37:24.594545","name":"Airflow","code":"airflow","role":["airflow"],"type":"web","default":true,"popularity":null,"descr":null,"ports":{"private":["31"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":150,"height":150,"image":"7f41d873-2de5-4c6e-a037-42eeff572db6.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\">Airflow description </p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.0","ram_size":null,"disk_size":null,"dockerhub_image":"airflow","form":null,"category":[null],"group":[],"versions":[{"_id":425,"name":"Airflow","version":"2.7.3","update_status":"published","tag":"latest"},{"_id":426,"name":"Airflow","version":"2.7.1-unstable","update_status":"published","tag":"stable"}],"links":[],"domain":"latestports.com","sharedPorts":[{"host_port":"5000","container_port":"5000"}],"main":true,"version":{"_id":426,"name":"Airflow","version":"2.7.1-unstable","update_status":"published","tag":"stable"},"environment":[{"ENV_VAR1":"ENV_VAR1_VALUE"}],"network":"testnetwork","restart":"always","volumes":[{"host_path":"/airflow/host/path/","container_path":"/airflow/container/path"}]},{"category":[{"name":"integrated_features"}],"name":"CustomNginx","code":"customnginx","domain":"customnginx.latestports.com","sharedPorts":[{"host_port":"80","container_port":"8080"},{"host_port":"443","container_port":"8443"}],"versions":[],"custom":true,"type":"web","main":false,"_id":0,"dockerhub_user":"trydirect","dockerhub_name":"nginx","disk_size":"0.9Gb","ram_size":"1.3Gb","cpu":"0.3","volumes":[{"host_path":"asdfasdf","container_path":"asdfasdf"}],"environment":[{"CUSTOM_NGINX_ENV_VAR1":"CUSTOM_NGINX_ENV_VAR1_VALUE"},{"/customnginx/host/path":"/customnginx/container/path"}],"network":"testnetwork","restart":"always"}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\"><strong style=\"font-weight:bold\">Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)</strong></p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.6","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","form":null,"category":[{"name":"integrated_features"}],"group":[],"versions":[{"_id":456,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"}],"links":[],"version":{"_id":456,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"},"sharedPorts":[{"host_port":"9000","container_port":"9000"}],"environment":[{"PORTAINER_ENV_VAR1":"PORTAINER_ENV_VAR1_VALUE"}],"volumes":[{"host_path":"/portainer/test/path","container_path":"/portainer/test/path"}],"network":"testnetwork","restart":"always"}],"service":[{"_etag":null,"_id":24,"_created":"2020-06-19T13:07:24.228389","_updated":"2023-11-23T10:18:28.095813","name":"PostgreSQL","code":"postgres","role":["postgres"],"type":"service","default":null,"popularity":null,"descr":null,"ports":{"private":["5432"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":576,"height":594,"image":"fd23f54c-e250-4228-8d56-7e5d93ffb925.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":null,"plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.0","ram_size":null,"disk_size":null,"dockerhub_image":"postgres","form":null,"category":[null],"group":[{"_etag":null,"_id":12,"_created":null,"_updated":null,"name":"Database","code":"database"}],"versions":[{"_id":458,"name":"15","version":"15","update_status":"published","tag":"15"},{"_id":288,"name":"10.22","version":"10.22","update_status":"published","tag":"10.22"},{"_id":303,"name":"13.8","version":"13.8","update_status":"published","tag":"13.8"},{"_id":266,"name":"11","version":"11","update_status":"published","tag":"11"},{"_id":267,"name":"12.12","version":"12.12","update_status":"published","tag":"12.12"},{"_id":38,"name":"14.5","version":"14.5","update_status":"published","tag":"14.5"},{"_id":596,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"}],"links":[],"domain":"","sharedPorts":[{"host_port":"5432","container_port":"5432"}],"main":false,"version":{"_id":596,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"},"environment":[{"POSTGRES_ENV_VAR1":"POSTGRES_ENV_VAR1_VALUE"},{"POSTGRES_ENV_VAR2":"POSTGRES_ENV_VAR2_VALUE"}],"network":"testnetwork","restart":"always","volumes":[{"host_path":"./pg.conf","container_path":"/etc/postgres/pg.conf"}]}],"servers_count":3,"project_name":"LatestPorts","custom_stack_code":"latestports","project_git_url":"https://github.com/latestports","custom_stack_category":[],"networks":["testnetwork","testnetwork2"]}}
\ No newline at end of file
+{"commonDomain":"justates222t.com","domainList":{},"region":"fsn1","zone":null,"server":"cx21","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":["portainer_ce_feature"],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":true,"disk_type":"pd-standart","cloud_token":"***********","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"_etag":null,"_id":180,"_created":"2021-12-17T08:11:40.875486","_updated":"2023-11-21T17:37:24.594545","name":"Airflow","code":"airflow","role":["airflow"],"type":"web","default":true,"popularity":null,"descr":null,"ports":{"private":["31"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":150,"height":150,"image":"7f41d873-2de5-4c6e-a037-42eeff572db6.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\">Airflow description </p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.0","ram_size":null,"disk_size":null,"dockerhub_image":"airflow","form":null,"category":[null],"group":[],"versions":[{"_id":425,"name":"Airflow","version":"2.7.3","update_status":"published","tag":"latest"},{"_id":426,"name":"Airflow","version":"2.7.1-unstable","update_status":"published","tag":"stable"}],"links":[],"domain":"latestports.com","sharedPorts":[{"host_port":"5000","container_port":"5000"}],"main":true,"version":{"_id":426,"name":"Airflow","version":"2.7.1-unstable","update_status":"published","tag":"stable"},"environment":[{"ENV_VAR1":"ENV_VAR1_VALUE"}, {"ENV_VAR2":"ENV_VAR1_VALUe2"}],"network":"testnetwork","restart":"always","volumes":[{"host_path":"/airflow/host/path/","container_path":"/airflow/container/path"}, {"host_path":"/airflow/host/path2/","container_path":"/airflow/container/path2"}]},{"category":[{"name":"integrated_features"}],"name":"CustomNginx","code":"customnginx","domain":"customnginx.latestports.com","sharedPorts":[{"host_port":"80","container_port":"8080"},{"host_port":"443","container_port":"8443"}],"versions":[],"custom":true,"type":"web","main":false,"_id":0,"dockerhub_user":"trydirect","dockerhub_name":"nginx","disk_size":"0.9Gb","ram_size":"1.3Gb","cpu":"0.3","volumes":[{"host_path":"/host/path","container_path":"/container/path"}],"environment":[{"CUSTOM_NGINX_ENV_VAR1":"CUSTOM_NGINX_ENV_VAR1_VALUE"},{"CUSTOM_NGINX_VAR2": "CUSTOM_NGINX_VAR_VALUE2"}, {"CUSTOM_NGINX_ENV_VAR3":"CUSTOM_NGINX_ENV_VAR1_VALUE3"},{"CUSTOM_NGINX_VAR4": "CUSTOM_NGINX_VAR_VALUE4"}],"network":"testnetwork","restart":"always"}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\"><strong style=\"font-weight:bold\">Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)</strong></p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.6","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","form":null,"category":[{"name":"integrated_features"}],"group":[],"versions":[{"_id":456,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"}],"links":[],"version":{"_id":456,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"},"sharedPorts":[{"host_port":"9000","container_port":"9000"}],"environment":[{"PORTAINER_ENV_VAR1":"PORTAINER_ENV_VAR1_VALUE"}],"volumes":[{"host_path":"/portainer/test/path","container_path":"/portainer/test/path"}],"network":"testnetwork","restart":"always"}],"service":[{"_etag":null,"_id":24,"_created":"2020-06-19T13:07:24.228389","_updated":"2023-11-23T10:18:28.095813","name":"PostgreSQL","code":"postgres","role":["postgres"],"type":"service","default":null,"popularity":null,"descr":null,"ports":{"private":["5432"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":576,"height":594,"image":"fd23f54c-e250-4228-8d56-7e5d93ffb925.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":null,"plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.0","ram_size":null,"disk_size":null,"dockerhub_image":"postgres","form":null,"category":[null],"group":[{"_etag":null,"_id":12,"_created":null,"_updated":null,"name":"Database","code":"database"}],"versions":[{"_id":458,"name":"15","version":"15","update_status":"published","tag":"15"},{"_id":288,"name":"10.22","version":"10.22","update_status":"published","tag":"10.22"},{"_id":303,"name":"13.8","version":"13.8","update_status":"published","tag":"13.8"},{"_id":266,"name":"11","version":"11","update_status":"published","tag":"11"},{"_id":267,"name":"12.12","version":"12.12","update_status":"published","tag":"12.12"},{"_id":38,"name":"14.5","version":"14.5","update_status":"published","tag":"14.5"},{"_id":596,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"}],"links":[],"domain":"","sharedPorts":[{"host_port":"5432","container_port":"5432"}],"main":false,"version":{"_id":596,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"},"environment":[{"POSTGRES_ENV_VAR1":"POSTGRES_ENV_VAR1_VALUE"},{"POSTGRES_ENV_VAR2":"POSTGRES_ENV_VAR2_VALUE"}],"network":"testnetwork","restart":"always","volumes":[{"host_path":"./pg.conf","container_path":"/etc/postgres/pg.conf"}]}],"servers_count":3,"project_name":"LatestPorts","custom_stack_code":"latestports","project_git_url":"https://github.com/latestports","custom_stack_category":[],"networks":["testnetwork","testnetwork2"]}}
\ No newline at end of file

From 093a0c91373e8d2e250ebb86294ecc42905b7673 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 9 Dec 2023 08:46:46 +0200
Subject: [PATCH 047/284] issue-auth Arc<User>

---
 src/console/commands/appclient/new.rs |  2 +-
 src/middleware/trydirect.rs           |  3 ++-
 src/models/user.rs                    | 12 ------------
 src/routes/client/add.rs              | 13 +++++++------
 src/routes/client/disable.rs          |  3 ++-
 src/routes/client/enable.rs           |  3 ++-
 src/routes/client/update.rs           |  2 +-
 src/routes/rating/add.rs              |  3 ++-
 src/routes/stack/add.rs               |  3 ++-
 src/routes/stack/compose.rs           |  5 +++--
 src/routes/stack/deploy.rs            |  2 +-
 src/routes/stack/get.rs               |  5 +++--
 12 files changed, 26 insertions(+), 30 deletions(-)

diff --git a/src/console/commands/appclient/new.rs b/src/console/commands/appclient/new.rs
index 138f11f7..448ac788 100644
--- a/src/console/commands/appclient/new.rs
+++ b/src/console/commands/appclient/new.rs
@@ -32,7 +32,7 @@ impl crate::console::commands::CallableTrait for NewCommand {
                 email: "email".to_string(),
                 email_confirmed: true,
             };
-            crate::routes::client::add_handler_inner(user, settings, db_pool).await?;
+            crate::routes::client::add_handler_inner(&user.id, settings, db_pool).await?;
 
             Ok(())
         })
diff --git a/src/middleware/trydirect.rs b/src/middleware/trydirect.rs
index 3e1db806..340d06f7 100644
--- a/src/middleware/trydirect.rs
+++ b/src/middleware/trydirect.rs
@@ -3,6 +3,7 @@ use actix_web::{web, dev::ServiceRequest, Error, HttpMessage, error::{ErrorInter
 use actix_web_httpauth::extractors::bearer::BearerAuth;
 use futures::future::{FutureExt};
 use reqwest::header::{ACCEPT, CONTENT_TYPE};
+use std::sync::Arc;
 
 #[tracing::instrument(name = "Trydirect bearer guard.")]
 pub async fn bearer_guard( req: ServiceRequest, credentials: BearerAuth) -> Result<ServiceRequest, (Error, ServiceRequest)> {
@@ -14,7 +15,7 @@ pub async fn bearer_guard( req: ServiceRequest, credentials: BearerAuth) -> Resu
     }
 
     let user = user.unwrap();
-    if req.extensions_mut().insert(user).is_some() {
+    if req.extensions_mut().insert(Arc::new(user)).is_some() {
         return Err((ErrorUnauthorized(JsonResponse::<i32>::build().set_msg("user already logged").to_string()), req));
     }
 
diff --git a/src/models/user.rs b/src/models/user.rs
index 4e77598a..8217ac80 100644
--- a/src/models/user.rs
+++ b/src/models/user.rs
@@ -10,15 +10,3 @@ pub struct User {
     // pub phone: Option<String>,
     // pub website: Option<String>,
 }
-
-impl Clone for User {
-    fn clone(&self) -> Self {
-        User {
-            id: self.id.clone(),
-            first_name: self.first_name.clone(),
-            last_name: self.last_name.clone(),
-            email: self.email.clone(),
-            email_confirmed: self.email_confirmed.clone(),
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/routes/client/add.rs b/src/routes/client/add.rs
index db987057..2a0757e0 100644
--- a/src/routes/client/add.rs
+++ b/src/routes/client/add.rs
@@ -6,22 +6,23 @@ use crate::models::user::User;
 use actix_web::{post, web, Responder, Result};
 use sqlx::PgPool;
 use tracing::Instrument;
+use std::sync::Arc;
 
 #[tracing::instrument(name = "Add client.")]
 #[post("")]
 pub async fn add_handler(
-    user: web::ReqData<User>,
+    user: web::ReqData<Arc<User>>,
     settings: web::Data<Settings>,
     pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
-    match add_handler_inner(user.into_inner(), settings, pool).await {
+    match add_handler_inner(&user.id, settings, pool).await {
         Ok(client) => JsonResponse::build().set_item(client).ok("Ok"),
         Err(msg) => JsonResponse::build().bad_request(msg),
     }
 }
 
 pub async fn add_handler_inner(
-    user: User,
+    user_id: &String,
     settings: web::Data<Settings>,
     pool: web::Data<PgPool>,
 ) -> Result<models::Client, String> {
@@ -34,7 +35,7 @@ pub async fn add_handler_inner(
         FROM client c 
         WHERE c.user_id = $1
         "#,
-        user.id.clone(),
+        user_id.clone(),
     )
     .fetch_one(pool.get_ref())
     .instrument(query_span)
@@ -45,7 +46,7 @@ pub async fn add_handler_inner(
             if client_count >= settings.max_clients_number {
                 tracing::error!(
                     "Too many clients. The user {} has {} clients",
-                    user.id,
+                    user_id,
                     client_count
                 );
 
@@ -59,7 +60,7 @@ pub async fn add_handler_inner(
     };
 
     let mut client = models::Client::default();
-    client.user_id = user.id.clone();
+    client.user_id = user_id.clone();
     client.secret = client::generate_secret(pool.get_ref(), 255)
         .await
         .map(|s| Some(s))?;
diff --git a/src/routes/client/disable.rs b/src/routes/client/disable.rs
index b0d2cdeb..c80df8c7 100644
--- a/src/routes/client/disable.rs
+++ b/src/routes/client/disable.rs
@@ -5,11 +5,12 @@ use crate::models::Client;
 use actix_web::{error::ErrorInternalServerError, put, web, Responder, Result};
 use sqlx::PgPool;
 use tracing::Instrument;
+use std::sync::Arc;
 
 #[tracing::instrument(name = "Disable client.")]
 #[put("/{id}/disable")]
 pub async fn disable_handler(
-    user: web::ReqData<User>,
+    user: web::ReqData<Arc<User>>,
     settings: web::Data<Settings>,
     pool: web::Data<PgPool>,
     path: web::Path<(i32,)>,
diff --git a/src/routes/client/enable.rs b/src/routes/client/enable.rs
index ef0548dc..de09451e 100644
--- a/src/routes/client/enable.rs
+++ b/src/routes/client/enable.rs
@@ -6,11 +6,12 @@ use crate::models::Client;
 use actix_web::{error::ErrorBadRequest, put, web, Responder, Result};
 use sqlx::PgPool;
 use tracing::Instrument;
+use std::sync::Arc;
 
 #[tracing::instrument(name = "Enable client.")]
 #[put("/{id}/enable")]
 pub async fn enable_handler(
-    user: web::ReqData<User>,
+    user: web::ReqData<Arc<User>>,
     settings: web::Data<Settings>,
     pool: web::Data<PgPool>,
     path: web::Path<(i32,)>,
diff --git a/src/routes/client/update.rs b/src/routes/client/update.rs
index 64604d30..7aa28ebc 100644
--- a/src/routes/client/update.rs
+++ b/src/routes/client/update.rs
@@ -10,7 +10,7 @@ use tracing::Instrument;
 #[tracing::instrument(name = "Update client.")]
 #[put("/{id}")]
 pub async fn update_handler(
-    user: web::ReqData<User>,
+    user: web::ReqData<Arc<User>>,
     settings: web::Data<Settings>,
     pool: web::Data<PgPool>,
     path: web::Path<(i32,)>,
diff --git a/src/routes/rating/add.rs b/src/routes/rating/add.rs
index b099cfe7..922cee0d 100644
--- a/src/routes/rating/add.rs
+++ b/src/routes/rating/add.rs
@@ -6,6 +6,7 @@ use crate::models::RateCategory;
 use actix_web::{post, web, Responder, Result};
 use sqlx::PgPool;
 use tracing::Instrument;
+use std::sync::Arc;
 
 // workflow
 // add, update, list, get(user_id), ACL,
@@ -15,7 +16,7 @@ use tracing::Instrument;
 #[tracing::instrument(name = "Add rating.")]
 #[post("")]
 pub async fn add_handler(
-    user: web::ReqData<User>,
+    user: web::ReqData<Arc<User>>,
     form: web::Json<forms::Rating>,
     pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs
index 8176cfa5..3e6aa6cb 100644
--- a/src/routes/stack/add.rs
+++ b/src/routes/stack/add.rs
@@ -14,12 +14,13 @@ use sqlx::PgPool;
 use std::str;
 use tracing::Instrument;
 use uuid::Uuid;
+use std::sync::Arc;
 
 #[tracing::instrument(name = "Add stack.")]
 #[post("")]
 pub async fn add(
     body: Bytes,
-    user: web::ReqData<User>,
+    user: web::ReqData<Arc<User>>,
     pool: Data<PgPool>,
 ) -> Result<impl Responder> {
     let body_bytes = actix_web::body::to_bytes(body).await.unwrap();
diff --git a/src/routes/stack/compose.rs b/src/routes/stack/compose.rs
index 7b2f8a4c..e52ebff1 100644
--- a/src/routes/stack/compose.rs
+++ b/src/routes/stack/compose.rs
@@ -13,11 +13,12 @@ use sqlx::PgPool;
 use std::str;
 use tracing::Instrument;
 use uuid::Uuid;
+use std::sync::Arc;
 
 #[tracing::instrument(name = "User's generate docker-compose.")]
 #[post("/{id}")]
 pub async fn add(
-    user: web::ReqData<User>,
+    user: web::ReqData<Arc<User>>,
     path: web::Path<(i32,)>,
     pool: Data<PgPool>,
 ) -> Result<impl Responder> {
@@ -70,7 +71,7 @@ pub async fn add(
 #[tracing::instrument(name = "Generate docker-compose. Admin")]
 #[get("/{id}/compose")]
 pub async fn admin(
-    user: web::ReqData<User>,
+    user: web::ReqData<Arc<User>>,
     path: web::Path<(i32,)>,
     pool: Data<PgPool>,
 ) -> Result<impl Responder> {
diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs
index 5c9fa66f..32439478 100644
--- a/src/routes/stack/deploy.rs
+++ b/src/routes/stack/deploy.rs
@@ -15,7 +15,7 @@ use std::sync::Arc;
 #[tracing::instrument(name = "Deploy for every user. Admin endpoint")]
 #[post("/{id}/deploy")]
 pub async fn add(
-    user: web::ReqData<User>,
+    user: web::ReqData<Arc<User>>,
     path: web::Path<(i32,)>,
     pool: Data<PgPool>,
     sets: Data<Settings>,
diff --git a/src/routes/stack/get.rs b/src/routes/stack/get.rs
index 2264a144..d38ab3dd 100644
--- a/src/routes/stack/get.rs
+++ b/src/routes/stack/get.rs
@@ -5,11 +5,12 @@ use actix_web::{get, web, Responder, Result};
 use sqlx::PgPool;
 use std::convert::From;
 use tracing::Instrument;
+use std::sync::Arc;
 
 #[tracing::instrument(name = "Get logged user stack.")]
 #[get("/{id}")]
 pub async fn item(
-    user: web::ReqData<User>,
+    user: web::ReqData<Arc<User>>,
     path: web::Path<(i32,)>,
     pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
@@ -43,7 +44,7 @@ pub async fn item(
 #[tracing::instrument(name = "Get user's stack list.")]
 #[get("/user/{id}")]
 pub async fn list(
-    user: web::ReqData<User>,
+    user: web::ReqData<Arc<User>>,
     path: web::Path<(String,)>,
     pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {

From dbe5a06cf50f0c2ba3d5da48d1d89ca9738cd67a Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Sat, 9 Dec 2023 17:04:46 +0200
Subject: [PATCH 048/284] walk through app types optimized

---
 src/helpers/stack/builder.rs | 177 ++++++++++++++---------------------
 1 file changed, 71 insertions(+), 106 deletions(-)

diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index e718dd19..e7826f22 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -2,7 +2,8 @@ use std::collections::HashMap;
 use indexmap::IndexMap;
 use crate::helpers::stack::dctypes::{Compose, Port, Ports, PublishedPort, Service, Services, Volumes, Environment, Entrypoint, AdvancedVolumes, SingleValue};
 use serde_yaml;
-use crate::forms::{StackForm, stack};
+use crate::forms;
+use crate::forms::{StackForm, stack, Web, Feature, App};
 use crate::models::stack::Stack;
 #[derive(Clone, Debug)]
 struct Config {}
@@ -32,7 +33,7 @@ pub struct DcBuilder {
     pub(crate) stack: Stack
 }
 
-impl TryInto<AdvancedVolumes> for &stack::Volume {
+impl TryInto<AdvancedVolumes> for stack::Volume {
     type Error = String;
     fn try_into(self) -> Result<AdvancedVolumes, Self::Error> {
 
@@ -51,7 +52,7 @@ impl TryInto<AdvancedVolumes> for &stack::Volume {
     }
 }
 
-impl TryInto<Port> for &stack::Port {
+impl TryInto<Port> for stack::Port {
     type Error = String;
     fn try_into(self) -> Result<Port, Self::Error> {
         let cp  = self.container_port.clone()
@@ -92,21 +93,63 @@ fn convert_shared_ports(ports: Option<Vec<stack::Port>>) -> Result<Vec<Port>, St
     Ok(_ports)
 }
 
-// impl TryInto<IndexMap<String, Option<SingleValue>>> for HashMap<String, String> {
-//     type Error = String;
+trait TryIntoService {
+    fn try_into_service(&self) -> Service;
+}
+
+impl TryIntoService for App {
+    fn try_into_service(&self) -> Service {
+        let mut service = Service {
+            image: Some(self.docker_image.to_string()),
+            ..Default::default()
+        };
+
+        let ports: Vec<Port> = self.ports
+            .clone()
+            .unwrap_or_default()
+            .into_iter()
+            .map(|x| x.try_into().unwrap())
+            .collect();
+
+        let volumes: Vec<AdvancedVolumes> = self.volumes
+            .clone()
+            .unwrap_or_default()
+            .into_iter()
+            .map(|x| x.try_into().unwrap())
+            .collect();
+
+        let mut envs = IndexMap::new();
+        for item in self.environment.environment.clone().unwrap_or_default() {
+            let items = item
+                .into_iter()
+                .map(|(k, v)| (k, Some(SingleValue::String(v.clone()))))
+                .collect::<IndexMap<_,_>>();
+
+            envs.extend(items);
+        }
+
+        service.ports = Ports::Long(ports);
+        service.restart = Some("always".to_owned());
+        service.volumes = Volumes::Advanced(volumes);
+        service.environment = Environment::KvPair(envs);
+
+        service
+    }
+}
+
+// fn create_service<T>(app_type: T) -> Service
+//     where
+//         T: TryIntoService,
+// {
 //
-//     fn try_into(self) -> Result<IndexMap<String, Option<SingleValue>>, Self::Error> {
-//         let mut index_map = IndexMap::new();
+//     let mut service = Service {
+//         image: Some(app_type.try_into_service().image.unwrap_or_default()),
+//         ..Default::default()
+//     };
 //
-//         for (key, value) in self {
-//             let single_value = Some(SingleValue::String(value));
-//             index_map.insert(key, single_value);
-//         }
+//     service
 //
-//         Ok(index_map)
-//     }
 // }
-
 impl DcBuilder {
 
     pub fn new(stack: Stack) -> Self {
@@ -118,119 +161,41 @@ impl DcBuilder {
 
 
     pub fn build(&self) -> Option<String> {
-
         tracing::debug!("Start build docker compose from {:?}", &self.stack.body);
         let _stack = serde_json::from_value::<StackForm>(self.stack.body.clone());
         let mut services = indexmap::IndexMap::new();
-        match _stack  {
-            Ok(apps) => {
-                // tracing::debug!("stack item {:?}", apps.custom.web);
 
+        match _stack {
+            Ok(apps) => {
                 for app_type in apps.custom.web {
-                    let code = app_type.app.code.clone().to_owned();
-                    let mut service = Service {
-                        image: Some(app_type.app.docker_image.to_string()),
-                        ..Default::default()
-                    };
-
-                    let ports: Vec<Port> = app_type.app.ports
-                        .unwrap()
-                        .iter()
-                        .map(|x| x.try_into().unwrap())
-                        .collect();
-
-                    let volumes: Vec<AdvancedVolumes> = app_type.app.volumes
-                        .unwrap()
-                        .iter()
-                        .map(|x| x.try_into().unwrap())
-                        .collect();
-
-                    let mut envs = IndexMap::new();
-                    for item in app_type.app.environment.environment.unwrap() {
-                        let items = item
-                            .into_iter()
-                            .map(|(k, v)| (k, Some(SingleValue::String(v.clone()))))
-                            .collect::<IndexMap<_, _>>();
-
-                        envs.extend(items);
-                    }
-                    // tracing::debug!("envs: {:?}", envs);
-                    service.ports = Ports::Long(ports);
-                    service.restart = Some("always".to_owned());
-                    service.volumes = Volumes::Advanced(volumes);
-                    service.environment = Environment::KvPair(envs);
-                    // tracing::debug!("service 1 {:?}", &service);
-                    services.insert(
-                        code,
-                        Some(service),
-                    );
+                    // let service = create_service(app_type.app.clone());
+                    let service = app_type.app.try_into_service();
+                    services.insert(app_type.app.code.clone().to_owned(), Some(service));
                 }
 
-
                 if let Some(srvs) = apps.custom.service {
-
-                    if !srvs.is_empty() {
-
-                        for app_type in srvs {
-                            let code = app_type.app.code.to_owned();
-                            let mut service = Service {
-                                image: Some(app_type.app.docker_image.to_string()),
-                                ..Default::default()
-                            };
-
-                            let ports: Vec<Port> = app_type.app.ports
-                                .unwrap()
-                                .iter()
-                                .map(|x| x.try_into().unwrap())
-                                .collect();
-
-                            service.ports = Ports::Long(ports);
-                            service.restart = Some("always".to_owned());
-                            services.insert(
-                                code,
-                                Some(service),
-                            );
-                        }
-                        // tracing::debug!("services {:?}", services);
+                    for app_type in srvs {
+                        // let service = create_service(app_type.app.clone());
+                        let service = app_type.app.try_into_service();
+                        services.insert(app_type.app.code.clone().to_owned(), Some(service));
                     }
                 }
 
                 if let Some(features) = apps.custom.feature {
-
-                    if !features.is_empty() {
-
-                        for app_type in features {
-                            let code = app_type.app.code.to_owned();
-                            let mut service = Service {
-                                // image: Some(app.dockerhub_image.as_ref().unwrap().to_owned()),
-                                image: Some(app_type.app.docker_image.to_string()),
-                                ..Default::default()
-                            };
-
-                            let ports: Vec<Port> = app_type.app.ports
-                                .unwrap()
-                                .iter()
-                                .map(|x| x.try_into().unwrap())
-                                .collect();
-                            service.ports = Ports::Long(ports);
-                            service.restart = Some("always".to_owned());
-                            services.insert(
-                                code,
-                                Some(service),
-                            );
-                        }
-                        // tracing::debug!("services### {:?}", &service);
+                    for app_type in features {
+                        // let service = create_service(app_type.app.clone());
+                        let service = app_type.app.try_into_service();
+                        services.insert(app_type.app.code.clone().to_owned(), Some(service));
                     }
                 }
+
                 tracing::debug!("services {:?}", &services);
             }
             Err(e) => {
                 tracing::debug!("Unpack stack form error {:?}", e);
-                ()
             }
         }
 
-
         let compose_content = Compose {
             version: Some("3.8".to_string()),
             services: {

From f1d0c334b5b3df0b0ee1928b213185d6afdee434 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Sat, 9 Dec 2023 17:06:16 +0200
Subject: [PATCH 049/284] walk through app types optimized

---
 src/helpers/stack/builder.rs | 2 --
 1 file changed, 2 deletions(-)

diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index e7826f22..dc10fa07 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -1,8 +1,6 @@
-use std::collections::HashMap;
 use indexmap::IndexMap;
 use crate::helpers::stack::dctypes::{Compose, Port, Ports, PublishedPort, Service, Services, Volumes, Environment, Entrypoint, AdvancedVolumes, SingleValue};
 use serde_yaml;
-use crate::forms;
 use crate::forms::{StackForm, stack, Web, Feature, App};
 use crate::models::stack::Stack;
 #[derive(Clone, Debug)]

From 107a66bb743b541dd0ad3dd1c4e47dff2725c074 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 9 Dec 2023 19:48:00 +0200
Subject: [PATCH 050/284] issue-auth db_count_clients_by_user

---
 src/routes/client/add.rs | 58 ++++++++++++++++++----------------------
 1 file changed, 26 insertions(+), 32 deletions(-)

diff --git a/src/routes/client/add.rs b/src/routes/client/add.rs
index 2a0757e0..b07e8a75 100644
--- a/src/routes/client/add.rs
+++ b/src/routes/client/add.rs
@@ -26,38 +26,10 @@ pub async fn add_handler_inner(
     settings: web::Data<Settings>,
     pool: web::Data<PgPool>,
 ) -> Result<models::Client, String> {
-    let query_span = tracing::info_span!("Counting the user's clients");
-
-    match sqlx::query!(
-        r#"
-        SELECT
-            count(*) as client_count
-        FROM client c 
-        WHERE c.user_id = $1
-        "#,
-        user_id.clone(),
-    )
-    .fetch_one(pool.get_ref())
-    .instrument(query_span)
-    .await
-    {
-        Ok(result) => {
-            let client_count = result.client_count.unwrap();
-            if client_count >= settings.max_clients_number {
-                tracing::error!(
-                    "Too many clients. The user {} has {} clients",
-                    user_id,
-                    client_count
-                );
-
-                return Err("Too many clients created".to_string());
-            }
-        }
-        Err(e) => {
-            tracing::error!("Failed to execute query: {:?}", e);
-            return Err("Internal Server Error".to_string());
-        }
-    };
+    let client_count = db_count_client_by_user(pool.get_ref(), user_id).await?;
+    if client_count >= settings.max_clients_number {
+        return Err("Too many clients created".to_string());
+    }
 
     let mut client = models::Client::default();
     client.user_id = user_id.clone();
@@ -91,3 +63,25 @@ pub async fn add_handler_inner(
         }
     }
 }
+
+async fn db_count_client_by_user(pool: &PgPool , user_id: &String) -> Result<i64, String> {
+    let query_span = tracing::info_span!("Counting the user's clients");
+
+    sqlx::query!(
+        r#"
+        SELECT
+            count(*) as client_count
+        FROM client c 
+        WHERE c.user_id = $1
+        "#,
+        user_id.clone(),
+    )
+    .fetch_one(pool)
+    .instrument(query_span)
+    .await
+    .map(|result| {result.client_count.unwrap()})
+    .map_err(|err| {
+        tracing::error!("Failed to execute query: {:?}", err);
+        "Internal Server Error".to_string()
+    })
+}

From 581786d90701a463ca9408bce58e67618b65eec5 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 9 Dec 2023 20:22:53 +0200
Subject: [PATCH 051/284] issue-auth src/routes/client/add.rs

---
 src/models/mod.rs        |  1 +
 src/routes/client/add.rs | 73 +++++++++++++++++++++-------------------
 2 files changed, 39 insertions(+), 35 deletions(-)

diff --git a/src/models/mod.rs b/src/models/mod.rs
index 32ecf558..b6951144 100644
--- a/src/models/mod.rs
+++ b/src/models/mod.rs
@@ -6,3 +6,4 @@ pub mod user;
 pub use client::*;
 pub use rating::*;
 pub use stack::*;
+pub use user::*;
diff --git a/src/routes/client/add.rs b/src/routes/client/add.rs
index b07e8a75..8883f25e 100644
--- a/src/routes/client/add.rs
+++ b/src/routes/client/add.rs
@@ -2,7 +2,6 @@ use crate::configuration::Settings;
 use crate::helpers::client;
 use crate::helpers::JsonResponse;
 use crate::models;
-use crate::models::user::User;
 use actix_web::{post, web, Responder, Result};
 use sqlx::PgPool;
 use tracing::Instrument;
@@ -11,7 +10,7 @@ use std::sync::Arc;
 #[tracing::instrument(name = "Add client.")]
 #[post("")]
 pub async fn add_handler(
-    user: web::ReqData<Arc<User>>,
+    user: web::ReqData<Arc<models::User>>,
     settings: web::Data<Settings>,
     pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
@@ -26,45 +25,16 @@ pub async fn add_handler_inner(
     settings: web::Data<Settings>,
     pool: web::Data<PgPool>,
 ) -> Result<models::Client, String> {
-    let client_count = db_count_client_by_user(pool.get_ref(), user_id).await?;
+    let client_count = db_count_clients_by_user(pool.get_ref(), user_id).await?;
     if client_count >= settings.max_clients_number {
         return Err("Too many clients created".to_string());
     }
 
-    let mut client = models::Client::default();
-    client.user_id = user_id.clone();
-    client.secret = client::generate_secret(pool.get_ref(), 255)
-        .await
-        .map(|s| Some(s))?;
-
-    let query_span = tracing::info_span!("Saving new client into the database");
-    match sqlx::query!(
-        r#"
-        INSERT INTO client (user_id, secret, created_at, updated_at)
-        VALUES ($1, $2, NOW() at time zone 'utc', NOW() at time zone 'utc')
-        RETURNING id
-        "#,
-        client.user_id.clone(),
-        client.secret,
-    )
-    .fetch_one(pool.get_ref())
-    .instrument(query_span)
-    .await
-    {
-        Ok(result) => {
-            tracing::info!("New client {} have been saved to database", result.id);
-            client.id = result.id;
-
-            return Ok(client);
-        }
-        Err(e) => {
-            tracing::error!("Failed to execute query: {:?}", e);
-            return Err("Failed to insert".to_string());
-        }
-    }
+    let client = create_client(pool.get_ref(), user_id).await?;
+    db_insert_client(pool.get_ref(), client).await
 }
 
-async fn db_count_client_by_user(pool: &PgPool , user_id: &String) -> Result<i64, String> {
+async fn db_count_clients_by_user(pool: &PgPool , user_id: &String) -> Result<i64, String> {
     let query_span = tracing::info_span!("Counting the user's clients");
 
     sqlx::query!(
@@ -85,3 +55,36 @@ async fn db_count_client_by_user(pool: &PgPool , user_id: &String) -> Result<i64
         "Internal Server Error".to_string()
     })
 }
+
+async fn  db_insert_client(pool: &PgPool, mut client: models::Client) -> Result<models::Client, String> {
+    let query_span = tracing::info_span!("Saving new client into the database");
+    sqlx::query!(
+        r#"
+        INSERT INTO client (user_id, secret, created_at, updated_at)
+        VALUES ($1, $2, NOW() at time zone 'utc', NOW() at time zone 'utc')
+        RETURNING id
+        "#,
+        client.user_id.clone(),
+        client.secret,
+    )
+    .fetch_one(pool)
+    .instrument(query_span)
+    .await
+    .map(move |result| {
+        client.id = result.id;
+        client
+    }).map_err(|e| {
+        tracing::error!("Failed to execute query: {:?}", e);
+        "Failed to insert".to_string()
+    })
+}
+
+async fn create_client(pool: &PgPool, user_id: &String) -> Result<models::Client, String> {
+    let mut client = models::Client::default(); 
+    client.user_id = user_id.clone();
+    client.secret = client::generate_secret(pool, 255)
+        .await
+        .map(|s| Some(s))?;
+
+    Ok(client)
+}

From 7669952f1a773971a0fc603bf3533eaf7fd35fe6 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sun, 10 Dec 2023 08:48:33 +0200
Subject: [PATCH 052/284] issue-auth disable client route

---
 src/routes/client/disable.rs | 83 +++++++++++++++++++++---------------
 1 file changed, 49 insertions(+), 34 deletions(-)

diff --git a/src/routes/client/disable.rs b/src/routes/client/disable.rs
index c80df8c7..921eaa12 100644
--- a/src/routes/client/disable.rs
+++ b/src/routes/client/disable.rs
@@ -1,7 +1,6 @@
 use crate::configuration::Settings;
 use crate::helpers::JsonResponse;
-use crate::models::user::User;
-use crate::models::Client;
+use crate::models;
 use actix_web::{error::ErrorInternalServerError, put, web, Responder, Result};
 use sqlx::PgPool;
 use tracing::Instrument;
@@ -10,61 +9,77 @@ use std::sync::Arc;
 #[tracing::instrument(name = "Disable client.")]
 #[put("/{id}/disable")]
 pub async fn disable_handler(
-    user: web::ReqData<Arc<User>>,
+    user: web::ReqData<Arc<models::User>>,
     settings: web::Data<Settings>,
     pool: web::Data<PgPool>,
     path: web::Path<(i32,)>,
 ) -> Result<impl Responder> {
-    let client_id = path.0;
+    match async {
+        let client_id = path.0;
+        let mut client = db_fetch_client_by_id(pool.get_ref(), client_id).await?; 
+        if client.secret.is_none() {
+            return Err("client is not active".to_string());
+        }
+
+        client.secret = None;
+        db_update_client(pool.get_ref(), client).await
+    }.await {
+        Ok(msg) => {
+            JsonResponse::<models::Client>::build().ok(msg)
+        }
+        Err(msg) => {
+            JsonResponse::<models::Client>::build().bad_request(msg)
+        }
+    }
+}
+
+async fn db_fetch_client_by_id(pool: &PgPool, id: i32) -> Result<models::Client, String> {
     let query_span = tracing::info_span!("Fetching the client by ID");
-    let mut client: Client = match sqlx::query_as!(
-        Client,
+    sqlx::query_as!(
+        models::Client,
         r#"
         SELECT
            id, user_id, secret 
         FROM client c
         WHERE c.id = $1
         "#,
-        client_id,
+        id,
     )
-    .fetch_one(pool.get_ref())
+    .fetch_one(pool)
     .instrument(query_span)
     .await
-    {
-        Ok(client) if client.secret.is_some() => Ok(client),
-        Ok(_client) => Err("client is not active"),
-        Err(sqlx::Error::RowNotFound) => Err("client not found"),
-        Err(e) => {
-            tracing::error!("Failed to execute fetch query: {:?}", e);
-            Err("")
+    .map_err(|e| {
+        match e {
+            sqlx::Error::RowNotFound => "client not found".to_string(),
+            s => {
+                tracing::error!("Failed to execute fetch query: {:?}", s);
+                "".to_string()
+            }
         }
-    }
-    .map_err(|s| {
-        ErrorInternalServerError(JsonResponse::<Client>::build().set_msg(s).to_string())
-    })?;
+    })
+}
 
-    client.secret = None;
+async fn db_update_client(pool: &PgPool, client: models::Client) -> Result<String , String> {
     let query_span = tracing::info_span!("Updating client into the database");
-    match sqlx::query!(
+    sqlx::query!(
         r#"
         UPDATE client SET 
-            secret=null,
+            secret=$1,
             updated_at=NOW() at time zone 'utc'
-        WHERE id = $1
+        WHERE id = $2
         "#,
+        client.secret,
         client.id
     )
-    .execute(pool.get_ref())
+    .execute(pool)
     .instrument(query_span)
     .await
-    {
-        Ok(_) => {
-            tracing::info!("Client {} have been saved to database", client.id);
-            JsonResponse::build().set_item(client).ok("success")
-        }
-        Err(e) => {
-            tracing::error!("Failed to execute query: {:?}", e);
-            JsonResponse::build().bad_request("")
-        }
-    }
+    .map(|_|{
+        tracing::info!("Client {} have been saved to database", client.id);
+        "success".to_string()
+    })
+    .map_err(|err| {
+        tracing::error!("Failed to execute query: {:?}", err);
+        "".to_string()
+    })
 }

From e8f63e6d32133ae8d911ac215d242c707afa95bf Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sun, 10 Dec 2023 09:04:44 +0200
Subject: [PATCH 053/284] issue-auth optimize /client/{id}/enable

---
 src/routes/client/disable.rs | 10 ++---
 src/routes/client/enable.rs  | 83 ++++++++++++++++++++----------------
 2 files changed, 52 insertions(+), 41 deletions(-)

diff --git a/src/routes/client/disable.rs b/src/routes/client/disable.rs
index 921eaa12..a513bd53 100644
--- a/src/routes/client/disable.rs
+++ b/src/routes/client/disable.rs
@@ -1,7 +1,7 @@
 use crate::configuration::Settings;
 use crate::helpers::JsonResponse;
 use crate::models;
-use actix_web::{error::ErrorInternalServerError, put, web, Responder, Result};
+use actix_web::{put, web, Responder, Result};
 use sqlx::PgPool;
 use tracing::Instrument;
 use std::sync::Arc;
@@ -24,8 +24,8 @@ pub async fn disable_handler(
         client.secret = None;
         db_update_client(pool.get_ref(), client).await
     }.await {
-        Ok(msg) => {
-            JsonResponse::<models::Client>::build().ok(msg)
+        Ok(client) => {
+            JsonResponse::build().set_item(client).ok("success")
         }
         Err(msg) => {
             JsonResponse::<models::Client>::build().bad_request(msg)
@@ -59,7 +59,7 @@ async fn db_fetch_client_by_id(pool: &PgPool, id: i32) -> Result<models::Client,
     })
 }
 
-async fn db_update_client(pool: &PgPool, client: models::Client) -> Result<String , String> {
+async fn db_update_client(pool: &PgPool, client: models::Client) -> Result<models::Client , String> {
     let query_span = tracing::info_span!("Updating client into the database");
     sqlx::query!(
         r#"
@@ -76,7 +76,7 @@ async fn db_update_client(pool: &PgPool, client: models::Client) -> Result<Strin
     .await
     .map(|_|{
         tracing::info!("Client {} have been saved to database", client.id);
-        "success".to_string()
+        client
     })
     .map_err(|err| {
         tracing::error!("Failed to execute query: {:?}", err);
diff --git a/src/routes/client/enable.rs b/src/routes/client/enable.rs
index de09451e..6f10bebe 100644
--- a/src/routes/client/enable.rs
+++ b/src/routes/client/enable.rs
@@ -1,9 +1,8 @@
 use crate::configuration::Settings;
 use crate::helpers::client;
 use crate::helpers::JsonResponse;
-use crate::models::user::User;
-use crate::models::Client;
-use actix_web::{error::ErrorBadRequest, put, web, Responder, Result};
+use crate::models;
+use actix_web::{put, web, Responder, Result};
 use sqlx::PgPool;
 use tracing::Instrument;
 use std::sync::Arc;
@@ -11,45 +10,59 @@ use std::sync::Arc;
 #[tracing::instrument(name = "Enable client.")]
 #[put("/{id}/enable")]
 pub async fn enable_handler(
-    user: web::ReqData<Arc<User>>,
+    user: web::ReqData<Arc<models::User>>,
     settings: web::Data<Settings>,
     pool: web::Data<PgPool>,
     path: web::Path<(i32,)>,
 ) -> Result<impl Responder> {
-    let client_id = path.0;
+    match async {
+        let client_id = path.0;
+        let mut client = db_fetch_client_by_id(pool.get_ref(), client_id).await?; 
+        if client.secret.is_some() {
+            return Err("client is already active".to_string());
+        }
+
+        client.secret = Some(client::generate_secret(pool.get_ref(), 255).await?);
+        db_update_client(pool.get_ref(), client).await
+    }.await {
+        Ok(client) => {
+            JsonResponse::build().set_item(client).ok("success")
+        }
+        Err(msg) => {
+            JsonResponse::<models::Client>::build().bad_request(msg)
+        }
+    }
+}
+
+async fn db_fetch_client_by_id(pool: &PgPool, id: i32) -> Result<models::Client, String> {
     let query_span = tracing::info_span!("Fetching the client by ID");
-    let mut client: Client = match sqlx::query_as!(
-        Client,
+    sqlx::query_as!(
+        models::Client,
         r#"
         SELECT
            id, user_id, secret 
         FROM client c
         WHERE c.id = $1
         "#,
-        client_id,
+        id,
     )
-    .fetch_one(pool.get_ref())
+    .fetch_one(pool)
     .instrument(query_span)
     .await
-    {
-        Ok(client) if client.secret.is_none() => Ok(client),
-        Ok(_client) => Err("client is already enabled"),
-        Err(sqlx::Error::RowNotFound) => Err("the client is not found"),
-        Err(e) => {
-            tracing::error!("Failed to execute fetch query: {:?}", e);
-
-            Err("")
+    .map_err(|e| {
+        match e {
+            sqlx::Error::RowNotFound => "client not found".to_string(),
+            s => {
+                tracing::error!("Failed to execute fetch query: {:?}", s);
+                "".to_string()
+            }
         }
-    }
-    .map_err(|s| ErrorBadRequest(JsonResponse::<Client>::build().set_msg(s).to_string()))?;
-
-    client.secret = client::generate_secret(pool.get_ref(), 255)
-        .await
-        .map(|s| Some(s))
-        .map_err(|s| ErrorBadRequest(s))?;
+    })
+}
 
+async fn db_update_client(pool: &PgPool, client: models::Client) -> Result<models::Client, String> {
     let query_span = tracing::info_span!("Updating client into the database");
-    match sqlx::query!(
+    sqlx::query!(
         r#"
         UPDATE client SET 
             secret=$1,
@@ -59,17 +72,15 @@ pub async fn enable_handler(
         client.secret,
         client.id
     )
-    .execute(pool.get_ref())
+    .execute(pool)
     .instrument(query_span)
     .await
-    {
-        Ok(_) => {
-            tracing::info!("Client {} have been saved to database", client.id);
-            JsonResponse::build().set_item(client).ok("success")
-        }
-        Err(e) => {
-            tracing::error!("Failed to execute query: {:?}", e);
-            JsonResponse::build().internal_server_error("")
-        }
-    }
+    .map(|_|{
+        tracing::info!("Client {} have been saved to database", client.id);
+        client
+    })
+    .map_err(|err| {
+        tracing::error!("Failed to execute query: {:?}", err);
+        "".to_string()
+    })
 }

From f9a31b12b6d3f37e2ba09211bdbfe7e387744b29 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Sun, 10 Dec 2023 11:46:47 +0200
Subject: [PATCH 054/284] compose networks, volumes hardcoded

---
 src/forms/stack.rs                 |  21 ++++--
 src/helpers/stack/builder.rs       | 102 +++++++++++++++++++++--------
 src/routes/stack/compose.rs        |   4 +-
 tests/custom-stack-payload-11.json |   2 +-
 4 files changed, 94 insertions(+), 35 deletions(-)

diff --git a/src/forms/stack.rs b/src/forms/stack.rs
index f09b0528..e0fc8760 100644
--- a/src/forms/stack.rs
+++ b/src/forms/stack.rs
@@ -193,9 +193,16 @@ pub struct Custom {
     pub project_name: String,
     pub project_overview: Option<String>,
     pub project_description: Option<String>,
-    pub networks: Option<Vec<String>>, // all networks
+    #[serde(flatten)]
+    pub networks: ComposeNetworks, // all networks
 }
 
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct Network {
+    name: String
+}
+
+
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct App {
     #[serde(rename = "_etag")]
@@ -253,7 +260,7 @@ pub struct App {
     #[serde(flatten)]
     pub environment: Environment,
     #[serde(flatten)]
-    pub network: Networks,
+    pub network: ServiceNetworks,
     // #[serde(flatten)]
     // pub ports: Ports,
     #[serde(rename(deserialize = "sharedPorts"))]
@@ -277,10 +284,14 @@ pub struct Volumes {
     volumes: Vec<Volume>
 }
 
+// pub(crate) type Networks = Option<Vec<String>>;
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct ServiceNetworks {
+    pub network: Option<Vec<String>>
+}
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct Networks {
-    //network: Option<Vec<String>>
-    network: Option<String>
+pub struct ComposeNetworks {
+    pub networks: Option<Vec<String>>
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index dc10fa07..1d93623b 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -1,7 +1,11 @@
 use indexmap::IndexMap;
-use crate::helpers::stack::dctypes::{Compose, Port, Ports, PublishedPort, Service, Services, Volumes, Environment, Entrypoint, AdvancedVolumes, SingleValue};
+use crate::helpers::stack::dctypes::{Compose, Port, Ports, PublishedPort, Service, Services,
+                                     Volumes, Environment, Entrypoint, AdvancedVolumes, SingleValue,
+                                     Networks, TopLevelVolumes, ComposeVolume, ComposeNetwork,
+                                     ComposeNetworks, MapOrEmpty, ComposeNetworkSettingDetails,
+                                     NetworkSettings};
 use serde_yaml;
-use crate::forms::{StackForm, stack, Web, Feature, App};
+use crate::forms::{StackForm, stack, App};
 use crate::models::stack::Stack;
 #[derive(Clone, Debug)]
 struct Config {}
@@ -71,6 +75,20 @@ impl TryInto<Port> for stack::Port {
     }
 }
 
+impl TryInto<Networks> for stack::ServiceNetworks {
+    type Error = ();
+    fn try_into(self) -> Result<Networks, Self::Error> {
+        Ok(Networks::Simple(self.network.unwrap()))
+    }
+}
+
+impl TryInto<Networks> for stack::ComposeNetworks {
+    type Error = ();
+    fn try_into(self) -> Result<Networks, Self::Error> {
+        Ok(Networks::Simple(self.networks.unwrap()))
+    }
+}
+
 
 fn convert_shared_ports(ports: Option<Vec<stack::Port>>) -> Result<Vec<Port>, String> {
     tracing::debug!("convert shared ports {:?}", &ports);
@@ -102,6 +120,11 @@ impl TryIntoService for App {
             ..Default::default()
         };
 
+        let networks: Networks = self.network
+            .clone()
+            .try_into()
+            .unwrap_or_default();
+
         let ports: Vec<Port> = self.ports
             .clone()
             .unwrap_or_default()
@@ -126,6 +149,7 @@ impl TryIntoService for App {
             envs.extend(items);
         }
 
+        service.networks = networks;
         service.ports = Ports::Long(ports);
         service.restart = Some("always".to_owned());
         service.volumes = Volumes::Advanced(volumes);
@@ -135,19 +159,36 @@ impl TryIntoService for App {
     }
 }
 
-// fn create_service<T>(app_type: T) -> Service
-//     where
-//         T: TryIntoService,
-// {
-//
-//     let mut service = Service {
-//         image: Some(app_type.try_into_service().image.unwrap_or_default()),
-//         ..Default::default()
-//     };
-//
-//     service
-//
-// }
+impl Into<IndexMap<String, MapOrEmpty<NetworkSettings>>> for stack::ComposeNetworks {
+    fn into(self) -> IndexMap<String, MapOrEmpty<NetworkSettings>> {
+
+        tracing::debug!("networks found {:?}", self.networks);
+        let mut networks = self.networks.unwrap_or(vec![]);
+        tracing::debug!("networks found {:?}", networks);
+        let networks = networks
+            .into_iter()
+            .map(|net| // ["testnetwork", "testnetwork2"]
+                (net,
+                 MapOrEmpty::Map(
+                     NetworkSettings {
+                         attachable: false,
+                         driver: None,
+                         driver_opts: Default::default(),
+                         enable_ipv6: false,
+                         internal: false,
+                         external: None,
+                         ipam: None,
+                         labels: Default::default(),
+                         name: Some("Anothername".to_string()),
+                     }
+                 ))
+            )
+            .collect::<IndexMap<String, _>>();
+        tracing::debug!("nets: {:?}", networks);
+        networks
+    }
+}
+
 impl DcBuilder {
 
     pub fn new(stack: Stack) -> Self {
@@ -160,20 +201,23 @@ impl DcBuilder {
 
     pub fn build(&self) -> Option<String> {
         tracing::debug!("Start build docker compose from {:?}", &self.stack.body);
+        let mut compose_content = Compose {
+            version: Some("3.8".to_string()),
+            ..Default::default()
+        };
         let _stack = serde_json::from_value::<StackForm>(self.stack.body.clone());
-        let mut services = indexmap::IndexMap::new();
+        let mut services = IndexMap::new();
+        let mut volumes = IndexMap::new();
 
         match _stack {
             Ok(apps) => {
                 for app_type in apps.custom.web {
-                    // let service = create_service(app_type.app.clone());
                     let service = app_type.app.try_into_service();
                     services.insert(app_type.app.code.clone().to_owned(), Some(service));
                 }
 
                 if let Some(srvs) = apps.custom.service {
                     for app_type in srvs {
-                        // let service = create_service(app_type.app.clone());
                         let service = app_type.app.try_into_service();
                         services.insert(app_type.app.code.clone().to_owned(), Some(service));
                     }
@@ -181,26 +225,30 @@ impl DcBuilder {
 
                 if let Some(features) = apps.custom.feature {
                     for app_type in features {
-                        // let service = create_service(app_type.app.clone());
                         let service = app_type.app.try_into_service();
                         services.insert(app_type.app.code.clone().to_owned(), Some(service));
                     }
                 }
 
-                tracing::debug!("services {:?}", &services);
+                let volume = ComposeVolume{
+                    driver: None,
+                    driver_opts: Default::default(),
+                    external: None,
+                    labels: Default::default(),
+                    name: Some("Testvolumes".to_string()),
+                };
+                volumes.insert("mykey".to_string(), MapOrEmpty::Map(volume));
+                compose_content.volumes = TopLevelVolumes(volumes);
+                compose_content.networks = ComposeNetworks(apps.custom.networks.into());
+
             }
             Err(e) => {
                 tracing::debug!("Unpack stack form error {:?}", e);
             }
         }
+        tracing::debug!("services {:?}", &services);
+        compose_content.services = Services(services);
 
-        let compose_content = Compose {
-            version: Some("3.8".to_string()),
-            services: {
-                Services(services)
-            },
-            ..Default::default()
-        };
 
         let fname= format!("./files/{}.yml", self.stack.stack_id);
         tracing::debug!("Saving docker compose to file {:?}", fname);
diff --git a/src/routes/stack/compose.rs b/src/routes/stack/compose.rs
index 321f88bb..ea768fb4 100644
--- a/src/routes/stack/compose.rs
+++ b/src/routes/stack/compose.rs
@@ -88,11 +88,11 @@ pub async fn admin(
         .await
     {
         Ok(stack) => {
-            tracing::info!("stack found: {:?}", stack.id);
+            tracing::info!("Record found: {:?}", stack.id);
             Some(stack)
         }
         Err(sqlx::Error::RowNotFound) => {
-            tracing::error!("Row not found 404");
+            tracing::error!("Record not found");
             None
         }
         Err(e) => {
diff --git a/tests/custom-stack-payload-11.json b/tests/custom-stack-payload-11.json
index ab0314d5..f5d7106b 100644
--- a/tests/custom-stack-payload-11.json
+++ b/tests/custom-stack-payload-11.json
@@ -1 +1 @@
-{"commonDomain":"justates222t.com","domainList":{},"region":"fsn1","zone":null,"server":"cx21","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":["portainer_ce_feature"],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":true,"disk_type":"pd-standart","cloud_token":"***********","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"_etag":null,"_id":180,"_created":"2021-12-17T08:11:40.875486","_updated":"2023-11-21T17:37:24.594545","name":"Airflow","code":"airflow","role":["airflow"],"type":"web","default":true,"popularity":null,"descr":null,"ports":{"private":["31"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":150,"height":150,"image":"7f41d873-2de5-4c6e-a037-42eeff572db6.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\">Airflow description </p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.0","ram_size":null,"disk_size":null,"dockerhub_image":"airflow","form":null,"category":[null],"group":[],"versions":[{"_id":425,"name":"Airflow","version":"2.7.3","update_status":"published","tag":"latest"},{"_id":426,"name":"Airflow","version":"2.7.1-unstable","update_status":"published","tag":"stable"}],"links":[],"domain":"latestports.com","sharedPorts":[{"host_port":"5000","container_port":"5000"}],"main":true,"version":{"_id":426,"name":"Airflow","version":"2.7.1-unstable","update_status":"published","tag":"stable"},"environment":[{"ENV_VAR1":"ENV_VAR1_VALUE"}, {"ENV_VAR2":"ENV_VAR1_VALUe2"}],"network":"testnetwork","restart":"always","volumes":[{"host_path":"/airflow/host/path/","container_path":"/airflow/container/path"}, {"host_path":"/airflow/host/path2/","container_path":"/airflow/container/path2"}]},{"category":[{"name":"integrated_features"}],"name":"CustomNginx","code":"customnginx","domain":"customnginx.latestports.com","sharedPorts":[{"host_port":"80","container_port":"8080"},{"host_port":"443","container_port":"8443"}],"versions":[],"custom":true,"type":"web","main":false,"_id":0,"dockerhub_user":"trydirect","dockerhub_name":"nginx","disk_size":"0.9Gb","ram_size":"1.3Gb","cpu":"0.3","volumes":[{"host_path":"/host/path","container_path":"/container/path"}],"environment":[{"CUSTOM_NGINX_ENV_VAR1":"CUSTOM_NGINX_ENV_VAR1_VALUE"},{"CUSTOM_NGINX_VAR2": "CUSTOM_NGINX_VAR_VALUE2"}, {"CUSTOM_NGINX_ENV_VAR3":"CUSTOM_NGINX_ENV_VAR1_VALUE3"},{"CUSTOM_NGINX_VAR4": "CUSTOM_NGINX_VAR_VALUE4"}],"network":"testnetwork","restart":"always"}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\"><strong style=\"font-weight:bold\">Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)</strong></p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.6","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","form":null,"category":[{"name":"integrated_features"}],"group":[],"versions":[{"_id":456,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"}],"links":[],"version":{"_id":456,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"},"sharedPorts":[{"host_port":"9000","container_port":"9000"}],"environment":[{"PORTAINER_ENV_VAR1":"PORTAINER_ENV_VAR1_VALUE"}],"volumes":[{"host_path":"/portainer/test/path","container_path":"/portainer/test/path"}],"network":"testnetwork","restart":"always"}],"service":[{"_etag":null,"_id":24,"_created":"2020-06-19T13:07:24.228389","_updated":"2023-11-23T10:18:28.095813","name":"PostgreSQL","code":"postgres","role":["postgres"],"type":"service","default":null,"popularity":null,"descr":null,"ports":{"private":["5432"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":576,"height":594,"image":"fd23f54c-e250-4228-8d56-7e5d93ffb925.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":null,"plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.0","ram_size":null,"disk_size":null,"dockerhub_image":"postgres","form":null,"category":[null],"group":[{"_etag":null,"_id":12,"_created":null,"_updated":null,"name":"Database","code":"database"}],"versions":[{"_id":458,"name":"15","version":"15","update_status":"published","tag":"15"},{"_id":288,"name":"10.22","version":"10.22","update_status":"published","tag":"10.22"},{"_id":303,"name":"13.8","version":"13.8","update_status":"published","tag":"13.8"},{"_id":266,"name":"11","version":"11","update_status":"published","tag":"11"},{"_id":267,"name":"12.12","version":"12.12","update_status":"published","tag":"12.12"},{"_id":38,"name":"14.5","version":"14.5","update_status":"published","tag":"14.5"},{"_id":596,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"}],"links":[],"domain":"","sharedPorts":[{"host_port":"5432","container_port":"5432"}],"main":false,"version":{"_id":596,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"},"environment":[{"POSTGRES_ENV_VAR1":"POSTGRES_ENV_VAR1_VALUE"},{"POSTGRES_ENV_VAR2":"POSTGRES_ENV_VAR2_VALUE"}],"network":"testnetwork","restart":"always","volumes":[{"host_path":"./pg.conf","container_path":"/etc/postgres/pg.conf"}]}],"servers_count":3,"project_name":"LatestPorts","custom_stack_code":"latestports","project_git_url":"https://github.com/latestports","custom_stack_category":[],"networks":["testnetwork","testnetwork2"]}}
\ No newline at end of file
+{"commonDomain":"justates222t.com","domainList":{},"region":"fsn1","zone":null,"server":"cx21","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":["portainer_ce_feature"],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":true,"disk_type":"pd-standart","cloud_token":"***********","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"_etag":null,"_id":180,"_created":"2021-12-17T08:11:40.875486","_updated":"2023-11-21T17:37:24.594545","name":"Airflow","code":"airflow","role":["airflow"],"type":"web","default":true,"popularity":null,"descr":null,"ports":{"private":["31"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":150,"height":150,"image":"7f41d873-2de5-4c6e-a037-42eeff572db6.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\">Airflow description </p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.0","ram_size":null,"disk_size":null,"dockerhub_image":"airflow","form":null,"category":[null],"group":[],"versions":[{"_id":425,"name":"Airflow","version":"2.7.3","update_status":"published","tag":"latest"},{"_id":426,"name":"Airflow","version":"2.7.1-unstable","update_status":"published","tag":"stable"}],"links":[],"domain":"latestports.com","sharedPorts":[{"host_port":"5000","container_port":"5000"}],"main":true,"version":{"_id":426,"name":"Airflow","version":"2.7.1-unstable","update_status":"published","tag":"stable"},"environment":[{"ENV_VAR1":"ENV_VAR1_VALUE"}, {"ENV_VAR2":"ENV_VAR1_VALUe2"}],"network":["testnetwork"],"restart":"always","volumes":[{"host_path":"/airflow/host/path/","container_path":"/airflow/container/path"}, {"host_path":"/airflow/host/path2/","container_path":"/airflow/container/path2"}]},{"category":[{"name":"integrated_features"}],"name":"CustomNginx","code":"customnginx","domain":"customnginx.latestports.com","sharedPorts":[{"host_port":"80","container_port":"8080"},{"host_port":"443","container_port":"8443"}],"versions":[],"custom":true,"type":"web","main":false,"_id":0,"dockerhub_user":"trydirect","dockerhub_name":"nginx","disk_size":"0.9Gb","ram_size":"1.3Gb","cpu":"0.3","volumes":[{"host_path":"/host/path","container_path":"/container/path"}],"environment":[{"CUSTOM_NGINX_ENV_VAR1":"CUSTOM_NGINX_ENV_VAR1_VALUE"},{"CUSTOM_NGINX_VAR2": "CUSTOM_NGINX_VAR_VALUE2"}, {"CUSTOM_NGINX_ENV_VAR3":"CUSTOM_NGINX_ENV_VAR1_VALUE3"},{"CUSTOM_NGINX_VAR4": "CUSTOM_NGINX_VAR_VALUE4"}],"network":["testnetwork"],"restart":"always"}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\"><strong style=\"font-weight:bold\">Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)</strong></p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.6","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","form":null,"category":[{"name":"integrated_features"}],"group":[],"versions":[{"_id":456,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"}],"links":[],"version":{"_id":456,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"},"sharedPorts":[{"host_port":"9000","container_port":"9000"}],"environment":[{"PORTAINER_ENV_VAR1":"PORTAINER_ENV_VAR1_VALUE"}],"volumes":[{"host_path":"/portainer/test/path","container_path":"/portainer/test/path"}],"network":["testnetwork"],"restart":"always"}],"service":[{"_etag":null,"_id":24,"_created":"2020-06-19T13:07:24.228389","_updated":"2023-11-23T10:18:28.095813","name":"PostgreSQL","code":"postgres","role":["postgres"],"type":"service","default":null,"popularity":null,"descr":null,"ports":{"private":["5432"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":576,"height":594,"image":"fd23f54c-e250-4228-8d56-7e5d93ffb925.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":null,"plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.0","ram_size":null,"disk_size":null,"dockerhub_image":"postgres","form":null,"category":[null],"group":[{"_etag":null,"_id":12,"_created":null,"_updated":null,"name":"Database","code":"database"}],"versions":[{"_id":458,"name":"15","version":"15","update_status":"published","tag":"15"},{"_id":288,"name":"10.22","version":"10.22","update_status":"published","tag":"10.22"},{"_id":303,"name":"13.8","version":"13.8","update_status":"published","tag":"13.8"},{"_id":266,"name":"11","version":"11","update_status":"published","tag":"11"},{"_id":267,"name":"12.12","version":"12.12","update_status":"published","tag":"12.12"},{"_id":38,"name":"14.5","version":"14.5","update_status":"published","tag":"14.5"},{"_id":596,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"}],"links":[],"domain":"","sharedPorts":[{"host_port":"5432","container_port":"5432"}],"main":false,"version":{"_id":596,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"},"environment":[{"POSTGRES_ENV_VAR1":"POSTGRES_ENV_VAR1_VALUE"},{"POSTGRES_ENV_VAR2":"POSTGRES_ENV_VAR2_VALUE"}],"network":["testnetwork"],"restart":"always","volumes":[{"host_path":"./pg.conf","container_path":"/etc/postgres/pg.conf"}]}],"servers_count":3,"project_name":"LatestPorts","custom_stack_code":"latestports","project_git_url":"https://github.com/latestports","custom_stack_category":[],"networks":["testnetwork","testnetwork2"]}}
\ No newline at end of file

From 24953ffde2ceba9bd7cdeb70b8cdba3fd04d37f7 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Mon, 11 Dec 2023 17:32:25 +0200
Subject: [PATCH 055/284] named volumes from each service

---
 src/helpers/stack/builder.rs       | 84 ++++++++++++++++++++++--------
 src/routes/stack/deploy.rs         |  1 -
 tests/custom-stack-payload-11.json |  2 +-
 3 files changed, 62 insertions(+), 25 deletions(-)

diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index 1d93623b..c7f18eb1 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -5,7 +5,7 @@ use crate::helpers::stack::dctypes::{Compose, Port, Ports, PublishedPort, Servic
                                      ComposeNetworks, MapOrEmpty, ComposeNetworkSettingDetails,
                                      NetworkSettings};
 use serde_yaml;
-use crate::forms::{StackForm, stack, App};
+use crate::forms::{StackForm, stack, App, Volume, Web};
 use crate::models::stack::Stack;
 #[derive(Clone, Debug)]
 struct Config {}
@@ -32,10 +32,10 @@ impl Default for Port{
 #[derive(Clone, Debug)]
 pub struct DcBuilder {
     config: Config,
-    pub(crate) stack: Stack
+    pub(crate) stack: Stack,
 }
 
-impl TryInto<AdvancedVolumes> for stack::Volume {
+impl TryInto<AdvancedVolumes> for Volume {
     type Error = String;
     fn try_into(self) -> Result<AdvancedVolumes, Self::Error> {
 
@@ -109,6 +109,16 @@ fn convert_shared_ports(ports: Option<Vec<stack::Port>>) -> Result<Vec<Port>, St
     Ok(_ports)
 }
 
+fn is_named_docker_volume(volume: &str) -> bool {
+    // Docker named volumes typically don't contain special characters or slashes
+    // They are alphanumeric and may include underscores or hyphens
+    let is_alphanumeric = volume
+        .chars()
+        .all(|c| c.is_alphanumeric() || c == '_' || c == '-');
+    let does_not_contain_slash = !volume.contains('/');
+    is_alphanumeric && does_not_contain_slash
+}
+
 trait TryIntoService {
     fn try_into_service(&self) -> Service;
 }
@@ -136,7 +146,9 @@ impl TryIntoService for App {
             .clone()
             .unwrap_or_default()
             .into_iter()
-            .map(|x| x.try_into().unwrap())
+            .map(|x| {
+                x.try_into().unwrap()
+            })
             .collect();
 
         let mut envs = IndexMap::new();
@@ -162,12 +174,11 @@ impl TryIntoService for App {
 impl Into<IndexMap<String, MapOrEmpty<NetworkSettings>>> for stack::ComposeNetworks {
     fn into(self) -> IndexMap<String, MapOrEmpty<NetworkSettings>> {
 
-        tracing::debug!("networks found {:?}", self.networks);
+        // tracing::debug!("networks found {:?}", self.networks);
         let mut networks = self.networks.unwrap_or(vec![]);
-        tracing::debug!("networks found {:?}", networks);
         let networks = networks
             .into_iter()
-            .map(|net| // ["testnetwork", "testnetwork2"]
+            .map(|net|
                 (net,
                  MapOrEmpty::Map(
                      NetworkSettings {
@@ -177,28 +188,56 @@ impl Into<IndexMap<String, MapOrEmpty<NetworkSettings>>> for stack::ComposeNetwo
                          enable_ipv6: false,
                          internal: false,
                          external: None,
+                         // external: Some(ComposeNetwork::Bool(true)),
                          ipam: None,
                          labels: Default::default(),
-                         name: Some("Anothername".to_string()),
+                         name: Some("default".to_string()),
                      }
                  ))
             )
             .collect::<IndexMap<String, _>>();
-        tracing::debug!("nets: {:?}", networks);
         networks
     }
 }
 
+
+pub fn extract_named_volumes(app: App) -> IndexMap<String, MapOrEmpty<ComposeVolume>> {
+
+    let mut named_volumes = IndexMap::default();
+
+        let volumes = app.volumes
+            .unwrap()
+            .into_iter()
+            .filter(|volume| is_named_docker_volume(
+                volume.host_path.clone().unwrap().as_str())
+            )
+            .map(|volume| {
+                let k = volume.host_path.clone().unwrap();
+                (k.clone(), MapOrEmpty::Map(ComposeVolume {
+                    driver: None,
+                    driver_opts: Default::default(),
+                    external: None,
+                    labels: Default::default(),
+                    name: Some(k.clone())
+                }))
+            })
+            .collect::<IndexMap<String, MapOrEmpty<ComposeVolume>>>();
+
+        named_volumes.extend(volumes);
+    // tracing::debug!("Named volumes: {:?}", named_volumes);
+
+    named_volumes
+}
+
 impl DcBuilder {
 
     pub fn new(stack: Stack) -> Self {
         DcBuilder {
             config: Config::default(),
-            stack: stack,
+            stack,
         }
     }
 
-
     pub fn build(&self) -> Option<String> {
         tracing::debug!("Start build docker compose from {:?}", &self.stack.body);
         let mut compose_content = Compose {
@@ -207,19 +246,21 @@ impl DcBuilder {
         };
         let _stack = serde_json::from_value::<StackForm>(self.stack.body.clone());
         let mut services = IndexMap::new();
-        let mut volumes = IndexMap::new();
+        let mut named_volumes = IndexMap::default();
 
         match _stack {
             Ok(apps) => {
-                for app_type in apps.custom.web {
+                for app_type in &apps.custom.web {
                     let service = app_type.app.try_into_service();
                     services.insert(app_type.app.code.clone().to_owned(), Some(service));
+                    named_volumes.extend(extract_named_volumes(app_type.app.clone()));
                 }
 
                 if let Some(srvs) = apps.custom.service {
                     for app_type in srvs {
                         let service = app_type.app.try_into_service();
                         services.insert(app_type.app.code.clone().to_owned(), Some(service));
+                        named_volumes.extend(extract_named_volumes(app_type.app.clone()));
                     }
                 }
 
@@ -227,19 +268,16 @@ impl DcBuilder {
                     for app_type in features {
                         let service = app_type.app.try_into_service();
                         services.insert(app_type.app.code.clone().to_owned(), Some(service));
+                        named_volumes.extend(extract_named_volumes(app_type.app.clone()));
                     }
                 }
 
-                let volume = ComposeVolume{
-                    driver: None,
-                    driver_opts: Default::default(),
-                    external: None,
-                    labels: Default::default(),
-                    name: Some("Testvolumes".to_string()),
-                };
-                volumes.insert("mykey".to_string(), MapOrEmpty::Map(volume));
-                compose_content.volumes = TopLevelVolumes(volumes);
-                compose_content.networks = ComposeNetworks(apps.custom.networks.into());
+                let networks = apps.custom.networks.clone();
+                compose_content.networks = ComposeNetworks(networks.into());
+
+                if !named_volumes.is_empty() {
+                    compose_content.volumes = TopLevelVolumes(named_volumes);
+                }
 
             }
             Err(e) => {
diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs
index b0f512c3..0f14dd89 100644
--- a/src/routes/stack/deploy.rs
+++ b/src/routes/stack/deploy.rs
@@ -15,7 +15,6 @@ use lapin::{
 use crate::configuration::Settings;
 use crate::helpers::JsonResponse;
 use crate::helpers::stack::builder::DcBuilder;
-use futures_lite::stream::StreamExt;
 use crate::forms::StackPayload;
 
 
diff --git a/tests/custom-stack-payload-11.json b/tests/custom-stack-payload-11.json
index f5d7106b..811f74f6 100644
--- a/tests/custom-stack-payload-11.json
+++ b/tests/custom-stack-payload-11.json
@@ -1 +1 @@
-{"commonDomain":"justates222t.com","domainList":{},"region":"fsn1","zone":null,"server":"cx21","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":["portainer_ce_feature"],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":true,"disk_type":"pd-standart","cloud_token":"***********","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"_etag":null,"_id":180,"_created":"2021-12-17T08:11:40.875486","_updated":"2023-11-21T17:37:24.594545","name":"Airflow","code":"airflow","role":["airflow"],"type":"web","default":true,"popularity":null,"descr":null,"ports":{"private":["31"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":150,"height":150,"image":"7f41d873-2de5-4c6e-a037-42eeff572db6.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\">Airflow description </p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.0","ram_size":null,"disk_size":null,"dockerhub_image":"airflow","form":null,"category":[null],"group":[],"versions":[{"_id":425,"name":"Airflow","version":"2.7.3","update_status":"published","tag":"latest"},{"_id":426,"name":"Airflow","version":"2.7.1-unstable","update_status":"published","tag":"stable"}],"links":[],"domain":"latestports.com","sharedPorts":[{"host_port":"5000","container_port":"5000"}],"main":true,"version":{"_id":426,"name":"Airflow","version":"2.7.1-unstable","update_status":"published","tag":"stable"},"environment":[{"ENV_VAR1":"ENV_VAR1_VALUE"}, {"ENV_VAR2":"ENV_VAR1_VALUe2"}],"network":["testnetwork"],"restart":"always","volumes":[{"host_path":"/airflow/host/path/","container_path":"/airflow/container/path"}, {"host_path":"/airflow/host/path2/","container_path":"/airflow/container/path2"}]},{"category":[{"name":"integrated_features"}],"name":"CustomNginx","code":"customnginx","domain":"customnginx.latestports.com","sharedPorts":[{"host_port":"80","container_port":"8080"},{"host_port":"443","container_port":"8443"}],"versions":[],"custom":true,"type":"web","main":false,"_id":0,"dockerhub_user":"trydirect","dockerhub_name":"nginx","disk_size":"0.9Gb","ram_size":"1.3Gb","cpu":"0.3","volumes":[{"host_path":"/host/path","container_path":"/container/path"}],"environment":[{"CUSTOM_NGINX_ENV_VAR1":"CUSTOM_NGINX_ENV_VAR1_VALUE"},{"CUSTOM_NGINX_VAR2": "CUSTOM_NGINX_VAR_VALUE2"}, {"CUSTOM_NGINX_ENV_VAR3":"CUSTOM_NGINX_ENV_VAR1_VALUE3"},{"CUSTOM_NGINX_VAR4": "CUSTOM_NGINX_VAR_VALUE4"}],"network":["testnetwork"],"restart":"always"}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\"><strong style=\"font-weight:bold\">Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)</strong></p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.6","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","form":null,"category":[{"name":"integrated_features"}],"group":[],"versions":[{"_id":456,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"}],"links":[],"version":{"_id":456,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"},"sharedPorts":[{"host_port":"9000","container_port":"9000"}],"environment":[{"PORTAINER_ENV_VAR1":"PORTAINER_ENV_VAR1_VALUE"}],"volumes":[{"host_path":"/portainer/test/path","container_path":"/portainer/test/path"}],"network":["testnetwork"],"restart":"always"}],"service":[{"_etag":null,"_id":24,"_created":"2020-06-19T13:07:24.228389","_updated":"2023-11-23T10:18:28.095813","name":"PostgreSQL","code":"postgres","role":["postgres"],"type":"service","default":null,"popularity":null,"descr":null,"ports":{"private":["5432"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":576,"height":594,"image":"fd23f54c-e250-4228-8d56-7e5d93ffb925.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":null,"plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.0","ram_size":null,"disk_size":null,"dockerhub_image":"postgres","form":null,"category":[null],"group":[{"_etag":null,"_id":12,"_created":null,"_updated":null,"name":"Database","code":"database"}],"versions":[{"_id":458,"name":"15","version":"15","update_status":"published","tag":"15"},{"_id":288,"name":"10.22","version":"10.22","update_status":"published","tag":"10.22"},{"_id":303,"name":"13.8","version":"13.8","update_status":"published","tag":"13.8"},{"_id":266,"name":"11","version":"11","update_status":"published","tag":"11"},{"_id":267,"name":"12.12","version":"12.12","update_status":"published","tag":"12.12"},{"_id":38,"name":"14.5","version":"14.5","update_status":"published","tag":"14.5"},{"_id":596,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"}],"links":[],"domain":"","sharedPorts":[{"host_port":"5432","container_port":"5432"}],"main":false,"version":{"_id":596,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"},"environment":[{"POSTGRES_ENV_VAR1":"POSTGRES_ENV_VAR1_VALUE"},{"POSTGRES_ENV_VAR2":"POSTGRES_ENV_VAR2_VALUE"}],"network":["testnetwork"],"restart":"always","volumes":[{"host_path":"./pg.conf","container_path":"/etc/postgres/pg.conf"}]}],"servers_count":3,"project_name":"LatestPorts","custom_stack_code":"latestports","project_git_url":"https://github.com/latestports","custom_stack_category":[],"networks":["testnetwork","testnetwork2"]}}
\ No newline at end of file
+{"commonDomain":"justates222t.com","domainList":{},"region":"fsn1","zone":null,"server":"cx21","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":["portainer_ce_feature"],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":true,"disk_type":"pd-standart","cloud_token":"***********","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"_etag":null,"_id":180,"_created":"2021-12-17T08:11:40.875486","_updated":"2023-11-21T17:37:24.594545","name":"Airflow","code":"airflow","role":["airflow"],"type":"web","default":true,"popularity":null,"descr":null,"ports":{"private":["31"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":150,"height":150,"image":"7f41d873-2de5-4c6e-a037-42eeff572db6.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\">Airflow description </p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.0","ram_size":null,"disk_size":null,"dockerhub_image":"airflow","form":null,"category":[null],"group":[],"versions":[{"_id":425,"name":"Airflow","version":"2.7.3","update_status":"published","tag":"latest"},{"_id":426,"name":"Airflow","version":"2.7.1-unstable","update_status":"published","tag":"stable"}],"links":[],"domain":"latestports.com","sharedPorts":[{"host_port":"5000","container_port":"5000"}],"main":true,"version":{"_id":426,"name":"Airflow","version":"2.7.1-unstable","update_status":"published","tag":"stable"},"environment":[{"ENV_VAR1":"ENV_VAR1_VALUE"}, {"ENV_VAR2":"ENV_VAR1_VALUe2"}],"network":["testnetwork"],"restart":"always","volumes":[{"host_path":"./airflow/host/path/","container_path":"/airflow/container/path"}, {"host_path":"./airflow/host/path2/","container_path":"/airflow/container/path2"}, {"host_path":"airflow","container_path":"/airflow/named/volume/path"}]},{"category":[{"name":"integrated_features"}],"name":"CustomNginx","code":"customnginx","domain":"customnginx.latestports.com","sharedPorts":[{"host_port":"80","container_port":"8080"},{"host_port":"443","container_port":"8443"}],"versions":[],"custom":true,"type":"web","main":false,"_id":0,"dockerhub_user":"trydirect","dockerhub_name":"nginx","disk_size":"0.9Gb","ram_size":"1.3Gb","cpu":"0.3","volumes":[{"host_path":"./host/path","container_path":"/container/path"}, {"host_path":  "namedvolume", "container_path": "/container/path/to/"}],"environment":[{"CUSTOM_NGINX_ENV_VAR1":"CUSTOM_NGINX_ENV_VAR1_VALUE"},{"CUSTOM_NGINX_VAR2": "CUSTOM_NGINX_VAR_VALUE2"}, {"CUSTOM_NGINX_ENV_VAR3":"CUSTOM_NGINX_ENV_VAR1_VALUE3"},{"CUSTOM_NGINX_VAR4": "CUSTOM_NGINX_VAR_VALUE4"}],"network":["testnetwork"],"restart":"always"}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\"><strong style=\"font-weight:bold\">Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)</strong></p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.6","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","form":null,"category":[{"name":"integrated_features"}],"group":[],"versions":[{"_id":456,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"}],"links":[],"version":{"_id":456,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"},"sharedPorts":[{"host_port":"9000","container_port":"9000"}],"environment":[{"PORTAINER_ENV_VAR1":"PORTAINER_ENV_VAR1_VALUE"}],"volumes":[{"host_path":"/portainer/test/path","container_path":"/portainer/test/path"}],"network":["testnetwork"],"restart":"always"}],"service":[{"_etag":null,"_id":24,"_created":"2020-06-19T13:07:24.228389","_updated":"2023-11-23T10:18:28.095813","name":"PostgreSQL","code":"postgres","role":["postgres"],"type":"service","default":null,"popularity":null,"descr":null,"ports":{"private":["5432"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":576,"height":594,"image":"fd23f54c-e250-4228-8d56-7e5d93ffb925.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":null,"plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.0","ram_size":null,"disk_size":null,"dockerhub_image":"postgres","form":null,"category":[null],"group":[{"_etag":null,"_id":12,"_created":null,"_updated":null,"name":"Database","code":"database"}],"versions":[{"_id":458,"name":"15","version":"15","update_status":"published","tag":"15"},{"_id":288,"name":"10.22","version":"10.22","update_status":"published","tag":"10.22"},{"_id":303,"name":"13.8","version":"13.8","update_status":"published","tag":"13.8"},{"_id":266,"name":"11","version":"11","update_status":"published","tag":"11"},{"_id":267,"name":"12.12","version":"12.12","update_status":"published","tag":"12.12"},{"_id":38,"name":"14.5","version":"14.5","update_status":"published","tag":"14.5"},{"_id":596,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"}],"links":[],"domain":"","sharedPorts":[{"host_port":"5432","container_port":"5432"}],"main":false,"version":{"_id":596,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"},"environment":[{"POSTGRES_ENV_VAR1":"POSTGRES_ENV_VAR1_VALUE"},{"POSTGRES_ENV_VAR2":"POSTGRES_ENV_VAR2_VALUE"}],"network":["testnetwork"],"restart":"always","volumes":[{"host_path":"./pg.conf","container_path":"/etc/postgres/pg.conf"}]}],"servers_count":3,"project_name":"LatestPorts","custom_stack_code":"latestports","project_git_url":"https://github.com/latestports","custom_stack_category":[],"networks":["testnetwork","testnetwork2"]}}
\ No newline at end of file

From 18d20a1bdbe4b63baab5be6f813ae6a6d7abdd3f Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Mon, 11 Dec 2023 18:03:45 +0200
Subject: [PATCH 056/284] issue-auth configuration.yaml.dist

---
 configuration.yaml.dist | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)
 create mode 100644 configuration.yaml.dist

diff --git a/configuration.yaml.dist b/configuration.yaml.dist
new file mode 100644
index 00000000..030dd49d
--- /dev/null
+++ b/configuration.yaml.dist
@@ -0,0 +1,17 @@
+#auth_url: http://127.0.0.1:8080/me
+app_host: 127.0.0.1
+app_port: 8000
+auth_url: https://dev.try.direct/server/user/oauth_server/api/me
+max_clients_number: 2
+database:
+  host: 127.0.0.1
+  port: 5432
+  username: postgres
+  password: postgres
+  database_name: stacker
+
+amqp:
+  host: 127.0.0.1
+  port: 5672
+  username: guest
+  password: guest

From 8ae6b9071834b861a0415f88a6e39fd654fcb5e3 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Mon, 11 Dec 2023 19:49:22 +0200
Subject: [PATCH 057/284] always set default_network for testing

---
 src/forms/stack.rs           |  1 +
 src/helpers/stack/builder.rs | 23 +++++++++++------------
 2 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/src/forms/stack.rs b/src/forms/stack.rs
index e0fc8760..b67a8566 100644
--- a/src/forms/stack.rs
+++ b/src/forms/stack.rs
@@ -289,6 +289,7 @@ pub struct Volumes {
 pub struct ServiceNetworks {
     pub network: Option<Vec<String>>
 }
+
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct ComposeNetworks {
     pub networks: Option<Vec<String>>
diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index c7f18eb1..1db77c54 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -78,18 +78,14 @@ impl TryInto<Port> for stack::Port {
 impl TryInto<Networks> for stack::ServiceNetworks {
     type Error = ();
     fn try_into(self) -> Result<Networks, Self::Error> {
-        Ok(Networks::Simple(self.network.unwrap()))
-    }
-}
-
-impl TryInto<Networks> for stack::ComposeNetworks {
-    type Error = ();
-    fn try_into(self) -> Result<Networks, Self::Error> {
-        Ok(Networks::Simple(self.networks.unwrap()))
+        let mut networks = vec!["default_network".to_string()];
+        if self.network.is_some() {
+            networks.append(&mut self.network.unwrap());
+        }
+        Ok(Networks::Simple(networks))
     }
 }
 
-
 fn convert_shared_ports(ports: Option<Vec<stack::Port>>) -> Result<Vec<Port>, String> {
     tracing::debug!("convert shared ports {:?}", &ports);
     let mut _ports: Vec<Port> = vec![];
@@ -175,7 +171,10 @@ impl Into<IndexMap<String, MapOrEmpty<NetworkSettings>>> for stack::ComposeNetwo
     fn into(self) -> IndexMap<String, MapOrEmpty<NetworkSettings>> {
 
         // tracing::debug!("networks found {:?}", self.networks);
-        let mut networks = self.networks.unwrap_or(vec![]);
+        let mut networks = vec!["default_network".to_string()];
+        if self.networks.is_some() {
+            networks.append(&mut self.networks.unwrap());
+        }
         let networks = networks
             .into_iter()
             .map(|net|
@@ -187,8 +186,8 @@ impl Into<IndexMap<String, MapOrEmpty<NetworkSettings>>> for stack::ComposeNetwo
                          driver_opts: Default::default(),
                          enable_ipv6: false,
                          internal: false,
-                         external: None,
-                         // external: Some(ComposeNetwork::Bool(true)),
+                         // external: None,
+                         external: Some(ComposeNetwork::Bool(true)),
                          ipam: None,
                          labels: Default::default(),
                          name: Some("default".to_string()),

From 95ccbd405df627c0362f0536ffde42e506293ddf Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Tue, 12 Dec 2023 17:43:38 +0200
Subject: [PATCH 058/284] issue-auth new approach for JsonResponse

---
 src/helpers/json.rs          | 21 ++++++++++-----------
 src/routes/client/add.rs     |  4 ++--
 src/routes/client/disable.rs | 22 ++++++++++------------
 src/routes/client/enable.rs  |  4 ++--
 src/routes/client/update.rs  | 17 ++++++++---------
 src/routes/rating/add.rs     | 10 +++++-----
 src/routes/rating/get.rs     | 12 ++++++------
 src/routes/stack/add.rs      | 13 ++++++-------
 src/routes/stack/compose.rs  | 13 +++++++------
 src/routes/stack/deploy.rs   |  4 ++--
 src/routes/stack/get.rs      | 14 +++++++-------
 src/routes/stack/update.rs   | 10 +++++-----
 src/routes/test/deploy.rs    |  2 +-
 13 files changed, 71 insertions(+), 75 deletions(-)

diff --git a/src/helpers/json.rs b/src/helpers/json.rs
index 8990788d..629f65e5 100644
--- a/src/helpers/json.rs
+++ b/src/helpers/json.rs
@@ -1,7 +1,6 @@
 use actix_web::error::{ErrorBadRequest, ErrorConflict, ErrorInternalServerError, ErrorNotFound};
 use actix_web::web::Json;
 use actix_web::Error;
-use actix_web::Result;
 use serde_derive::Serialize;
 
 #[derive(Serialize)]
@@ -64,30 +63,30 @@ where
         serde_json::to_string(&json_response).unwrap()
     }
 
-    pub(crate) fn ok<I: Into<String>>(self, msg: I) -> Result<Json<JsonResponse<T>>, Error> {
-        Ok(Json(self.set_msg(msg).to_json_response()))
+    pub(crate) fn ok<I: Into<String>>(self, msg: I) -> Json<JsonResponse<T>> {
+        Json(self.set_msg(msg).to_json_response())
     }
 
     pub(crate) fn bad_request<I: Into<String>>(
         self,
         msg: I,
-    ) -> Result<Json<JsonResponse<T>>, Error> {
-        Err(ErrorBadRequest(self.set_msg(msg).to_string()))
+    ) -> Error {
+        ErrorBadRequest(self.set_msg(msg).to_string())
     }
 
-    pub(crate) fn not_found<I: Into<String>>(self, msg: I) -> Result<Json<JsonResponse<T>>, Error> {
-        Err(ErrorNotFound(self.set_msg(msg).to_string()))
+    pub(crate) fn not_found<I: Into<String>>(self, msg: I) -> Error {
+        ErrorNotFound(self.set_msg(msg).to_string())
     }
 
     pub(crate) fn internal_server_error<I: Into<String>>(
         self,
         msg: I,
-    ) -> Result<Json<JsonResponse<T>>, Error> {
-        Err(ErrorInternalServerError(self.set_msg(msg).to_string()))
+    ) -> Error {
+        ErrorInternalServerError(self.set_msg(msg).to_string())
     }
 
-    pub(crate) fn conflict<I: Into<String>>(self, msg: I) -> Result<Json<JsonResponse<T>>, Error> {
-        Err(ErrorConflict(self.set_msg(msg).to_string()))
+    pub(crate) fn conflict<I: Into<String>>(self, msg: I) -> Error {
+        ErrorConflict(self.set_msg(msg).to_string())
     }
 }
 
diff --git a/src/routes/client/add.rs b/src/routes/client/add.rs
index 8883f25e..f922718d 100644
--- a/src/routes/client/add.rs
+++ b/src/routes/client/add.rs
@@ -15,8 +15,8 @@ pub async fn add_handler(
     pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
     match add_handler_inner(&user.id, settings, pool).await {
-        Ok(client) => JsonResponse::build().set_item(client).ok("Ok"),
-        Err(msg) => JsonResponse::build().bad_request(msg),
+        Ok(client) => Ok(JsonResponse::build().set_item(client).ok("Ok")),
+        Err(msg) => Err(JsonResponse::<models::Client>::build().bad_request(msg)),
     }
 }
 
diff --git a/src/routes/client/disable.rs b/src/routes/client/disable.rs
index a513bd53..c5d2ac32 100644
--- a/src/routes/client/disable.rs
+++ b/src/routes/client/disable.rs
@@ -14,23 +14,21 @@ pub async fn disable_handler(
     pool: web::Data<PgPool>,
     path: web::Path<(i32,)>,
 ) -> Result<impl Responder> {
-    match async {
         let client_id = path.0;
-        let mut client = db_fetch_client_by_id(pool.get_ref(), client_id).await?; 
+        let mut client = db_fetch_client_by_id(pool.get_ref(), client_id)
+            .await
+            .map_err(|msg| JsonResponse::<models::Client>::build().not_found(msg))
+            ?; 
         if client.secret.is_none() {
-            return Err("client is not active".to_string());
+            return Err(JsonResponse::<models::Client>::build().bad_request("client is not active"));
         }
 
         client.secret = None;
-        db_update_client(pool.get_ref(), client).await
-    }.await {
-        Ok(client) => {
-            JsonResponse::build().set_item(client).ok("success")
-        }
-        Err(msg) => {
-            JsonResponse::<models::Client>::build().bad_request(msg)
-        }
-    }
+        let client = db_update_client(pool.get_ref(), client)
+            .await
+            .map_err(|msg| JsonResponse::<models::Client>::build().bad_request(msg))?;
+
+        Ok(JsonResponse::build().set_item(client).ok("success"))
 }
 
 async fn db_fetch_client_by_id(pool: &PgPool, id: i32) -> Result<models::Client, String> {
diff --git a/src/routes/client/enable.rs b/src/routes/client/enable.rs
index 6f10bebe..59bde80a 100644
--- a/src/routes/client/enable.rs
+++ b/src/routes/client/enable.rs
@@ -26,10 +26,10 @@ pub async fn enable_handler(
         db_update_client(pool.get_ref(), client).await
     }.await {
         Ok(client) => {
-            JsonResponse::build().set_item(client).ok("success")
+            Ok(JsonResponse::build().set_item(client).ok("success"))
         }
         Err(msg) => {
-            JsonResponse::<models::Client>::build().bad_request(msg)
+            Err(JsonResponse::<models::Client>::build().bad_request(msg))
         }
     }
 }
diff --git a/src/routes/client/update.rs b/src/routes/client/update.rs
index 7aa28ebc..0fff40eb 100644
--- a/src/routes/client/update.rs
+++ b/src/routes/client/update.rs
@@ -1,6 +1,5 @@
 use crate::helpers::client;
-use crate::models::user::User;
-use crate::models::Client;
+use crate::models;
 use crate::{configuration::Settings, helpers::JsonResponse};
 use actix_web::{error::ErrorBadRequest, put, web, Responder, Result};
 use sqlx::PgPool;
@@ -10,15 +9,15 @@ use tracing::Instrument;
 #[tracing::instrument(name = "Update client.")]
 #[put("/{id}")]
 pub async fn update_handler(
-    user: web::ReqData<Arc<User>>,
+    user: web::ReqData<Arc<models::User>>,
     settings: web::Data<Settings>,
     pool: web::Data<PgPool>,
     path: web::Path<(i32,)>,
 ) -> Result<impl Responder> {
     let client_id = path.0;
     let query_span = tracing::info_span!("Fetching the client by ID");
-    let mut client: Client = match sqlx::query_as!(
-        Client,
+    let mut client: models::Client = match sqlx::query_as!(
+        models::Client,
         r#"
         SELECT
            id, user_id, secret 
@@ -40,12 +39,12 @@ pub async fn update_handler(
             Err("")
         }
     }
-    .map_err(|s| ErrorBadRequest(JsonResponse::<Client>::build().set_msg(s).to_string()))?;
+    .map_err(|s| ErrorBadRequest(JsonResponse::<models::Client>::build().set_msg(s).to_string()))?;
 
     client.secret = client::generate_secret(pool.get_ref(), 255)
         .await
         .map(|s| Some(s))
-        .map_err(|s| ErrorBadRequest(JsonResponse::<Client>::build().set_msg(s).to_string()))?;
+        .map_err(|s| ErrorBadRequest(JsonResponse::<models::Client>::build().set_msg(s).to_string()))?;
 
     let query_span = tracing::info_span!("Updating client into the database");
     match sqlx::query!(
@@ -64,11 +63,11 @@ pub async fn update_handler(
     {
         Ok(_) => {
             tracing::info!("Client {} have been saved to database", client.id);
-            JsonResponse::build().set_item(client).ok("success")
+            Ok(JsonResponse::<models::Client>::build().set_item(client).ok("success"))
         }
         Err(e) => {
             tracing::error!("Failed to execute query: {:?}", e);
-            JsonResponse::build().internal_server_error("")
+            Err(JsonResponse::<models::Client>::build().internal_server_error(""))
         }
     }
 }
diff --git a/src/routes/rating/add.rs b/src/routes/rating/add.rs
index 922cee0d..5e2e8744 100644
--- a/src/routes/rating/add.rs
+++ b/src/routes/rating/add.rs
@@ -35,7 +35,7 @@ pub async fn add_handler(
         }
         Err(e) => {
             tracing::error!("Failed to fetch product: {:?}, error: {:?}", form.obj_id, e);
-            return JsonResponse::<models::Rating>::build().bad_request("Object not found");
+            return Err(JsonResponse::<models::Rating>::build().bad_request("Object not found"));
         }
     };
 
@@ -58,12 +58,12 @@ pub async fn add_handler(
                 form.obj_id,
                 form.category
             );
-            return JsonResponse::build().conflict("Already rated");
+            return Err(JsonResponse::<models::Rating>::build().conflict("Already rated"));
         }
         Err(sqlx::Error::RowNotFound) => {}
         Err(e) => {
             tracing::error!("Failed to fetch rating, error: {:?}", e);
-            return JsonResponse::build().internal_server_error("Internal Server Error");
+            return Err(JsonResponse::<models::Rating>::build().internal_server_error("Internal Server Error"));
         }
     }
 
@@ -91,11 +91,11 @@ pub async fn add_handler(
         Ok(result) => {
             tracing::info!("New rating {} have been saved to database", result.id);
 
-            JsonResponse::build().set_id(result.id).ok("Saved")
+            Ok(JsonResponse::<models::Rating>::build().set_id(result.id).ok("Saved"))
         }
         Err(e) => {
             tracing::error!("Failed to execute query: {:?}", e);
-            JsonResponse::build().internal_server_error("Failed to insert")
+            Err(JsonResponse::<models::Rating>::build().internal_server_error("Failed to insert"))
         }
     }
 }
diff --git a/src/routes/rating/get.rs b/src/routes/rating/get.rs
index e781d9a8..49fccb47 100644
--- a/src/routes/rating/get.rs
+++ b/src/routes/rating/get.rs
@@ -29,14 +29,14 @@ pub async fn get_handler(
     {
         Ok(rating) => {
             tracing::info!("rating found: {:?}", rating.id);
-            return JsonResponse::build().set_item(Some(rating)).ok("OK");
+            return Ok(JsonResponse::build().set_item(Some(rating)).ok("OK"));
         }
         Err(sqlx::Error::RowNotFound) => {
-            return JsonResponse::build().not_found("");
+            return Err(JsonResponse::<models::Rating>::build().not_found(""));
         }
         Err(e) => {
             tracing::error!("Failed to fetch rating, error: {:?}", e);
-            return JsonResponse::build().internal_server_error("");
+            return Err(JsonResponse::<models::Rating>::build().internal_server_error(""));
         }
     }
 }
@@ -54,14 +54,14 @@ pub async fn list_handler(path: web::Path<()>, pool: web::Data<PgPool>) -> Resul
     {
         Ok(rating) => {
             tracing::info!("Ratings found: {:?}", rating.len());
-            return JsonResponse::build().set_list(rating).ok("OK");
+            return Ok(JsonResponse::build().set_list(rating).ok("OK"));
         }
         Err(sqlx::Error::RowNotFound) => {
-            return JsonResponse::build().not_found("");
+            return Err(JsonResponse::<models::Rating>::build().not_found(""));
         }
         Err(e) => {
             tracing::error!("Failed to fetch rating, error: {:?}", e);
-            return JsonResponse::build().internal_server_error("");
+            return Err(JsonResponse::<models::Rating>::build().internal_server_error(""));
         }
     }
 }
diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs
index 7a12bfe8..690a02a7 100644
--- a/src/routes/stack/add.rs
+++ b/src/routes/stack/add.rs
@@ -32,7 +32,7 @@ pub async fn add(
         Ok(f) => f,
         Err(_err) => {
             let msg = format!("Invalid data. {:?}", _err);
-            return JsonResponse::<StackForm>::build().bad_request(msg);
+            return Err(JsonResponse::<StackForm>::build().bad_request(msg));
         }
     };
 
@@ -51,13 +51,12 @@ pub async fn add(
     {
         Ok(record) => {
             tracing::info!("record exists: id: {}, name: {}", record.id, record.name);
-            return JsonResponse::build().conflict("Stack with that name already exists"
-                .to_owned());
+            return Err(JsonResponse::<models::Stack>::build().conflict("Stack with that name already exists"));
         }
         Err(sqlx::Error::RowNotFound) => {}
         Err(e) => {
             tracing::error!("Failed to fetch stack info, error: {:?}", e);
-            return JsonResponse::build().bad_request(format!("Internal Server Error"));
+            return Err(JsonResponse::<models::Stack>::build().bad_request("Internal Server Error"));
         }
     };
 
@@ -85,7 +84,7 @@ pub async fn add(
         let errors = form.validate().unwrap_err();
         let err_msg = format!("Invalid data received {:?}", &errors.to_string());
         tracing::debug!(err_msg);
-        return JsonResponse::build().bad_request(errors.to_string());// tmp solution
+        return Err(JsonResponse::<models::Stack>::build().bad_request(errors.to_string()));// tmp solution
     }
 
     let body: Value = match serde_json::to_value::<StackForm>(form) {
@@ -118,11 +117,11 @@ pub async fn add(
                 "req_id: {} New stack details have been saved to database",
                 request_id
             );
-            return JsonResponse::build().set_id(record.id).ok("OK");
+            return Ok(JsonResponse::<models::Stack>::build().set_id(record.id).ok("OK"));
         }
         Err(e) => {
             tracing::error!("req_id: {} Failed to execute query: {:?}", request_id, e);
-            return JsonResponse::build().internal_server_error("Internal Server Error");
+            return Err(JsonResponse::<models::Stack>::build().internal_server_error("Internal Server Error"));
         }
     };
 }
diff --git a/src/routes/stack/compose.rs b/src/routes/stack/compose.rs
index 819a8865..b92930b0 100644
--- a/src/routes/stack/compose.rs
+++ b/src/routes/stack/compose.rs
@@ -56,13 +56,14 @@ pub async fn add(
             let fc = dc.build();
             tracing::debug!("Docker compose file content {:?}", fc);
 
-            return JsonResponse::build()
+            return Ok(JsonResponse::build()
                 .set_id(id)
                 .set_item(fc.unwrap())
-                .ok("Success");
+                .ok("Success")
+                );
         }
         None => {
-            return JsonResponse::build().bad_request("Could not generate compose file");
+            return Err(JsonResponse::<Stack>::build().bad_request("Could not generate compose file"));
         }
     }
 }
@@ -117,13 +118,13 @@ pub async fn admin(
 
             };
             // tracing::debug!("Docker compose file content {:?}", fc);
-            return JsonResponse::build()
+            return Ok(JsonResponse::build()
                 .set_id(id)
-                .set_item(fc).ok("Success");
+                .set_item(fc).ok("Success"));
 
         }
         None => {
-            return JsonResponse::build().bad_request("Could not generate compose file");
+            return Err(JsonResponse::<Stack>::build().bad_request("Could not generate compose file"));
         }
     }
 }
diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs
index d1224c59..1d52caa7 100644
--- a/src/routes/stack/deploy.rs
+++ b/src/routes/stack/deploy.rs
@@ -91,10 +91,10 @@ pub async fn add(
 
             assert_eq!(confirm, Confirmation::NotRequested);
             tracing::debug!("Message sent to rabbitmq");
-            return JsonResponse::<Stack>::build().set_id(id).ok("Success");
+            return Ok(JsonResponse::<Stack>::build().set_id(id).ok("Success"));
         }
         None => {
-            JsonResponse::build().internal_server_error("Deployment failed")
+            Err(JsonResponse::<Stack>::build().internal_server_error("Deployment failed"))
         }
     }
 }
diff --git a/src/routes/stack/get.rs b/src/routes/stack/get.rs
index dc8c09f7..88114dc5 100644
--- a/src/routes/stack/get.rs
+++ b/src/routes/stack/get.rs
@@ -1,4 +1,4 @@
-use crate::helpers::{JsonResponse, JsonResponseBuilder};
+use crate::helpers::JsonResponse;
 use crate::models;
 use crate::models::user::User;
 use actix_web::{get, web, Responder, Result};
@@ -31,12 +31,12 @@ pub async fn item(
     {
         Ok(stack) => {
             tracing::info!("Stack found: {:?}", stack.id,);
-            return JsonResponse::build().set_item(Some(stack)).ok("OK");
+            return Ok(JsonResponse::build().set_item(Some(stack)).ok("OK"));
         }
-        Err(sqlx::Error::RowNotFound) => JsonResponse::build().not_found("Record not found"),
+        Err(sqlx::Error::RowNotFound) => Err(JsonResponse::<models::Stack>::build().not_found("Record not found")),
         Err(e) => {
             tracing::error!("Failed to fetch stack, error: {:?}", e);
-            return JsonResponse::build().internal_server_error("Could not fetch data");
+            return Err(JsonResponse::<models::Stack>::build().internal_server_error("Could not fetch data"));
         }
     }
 }
@@ -69,15 +69,15 @@ pub async fn list(
     .await
     {
         Ok(list) => {
-            return JsonResponse::build().set_list(list).ok("OK");
+            return Ok(JsonResponse::build().set_list(list).ok("OK"));
         }
         Err(sqlx::Error::RowNotFound) => {
             tracing::error!("No stacks found for user: {:?}", &user.id);
-            return JsonResponse::build().not_found("No stacks found for user");
+            return Err(JsonResponse::<models::Stack>::build().not_found("No stacks found for user"));
         }
         Err(e) => {
             tracing::error!("Failed to fetch stack, error: {:?}", e);
-            return JsonResponse::build().internal_server_error("Could not fetch".to_string());
+            return Err(JsonResponse::<models::Stack>::build().internal_server_error("Could not fetch"));
         }
     }
 }
diff --git a/src/routes/stack/update.rs b/src/routes/stack/update.rs
index d51c95ff..a418b532 100644
--- a/src/routes/stack/update.rs
+++ b/src/routes/stack/update.rs
@@ -43,8 +43,8 @@ pub async fn update(
         }
         Err(e) => {
             tracing::error!("Failed to fetch record: {:?}, error: {:?}", id, e);
-            return JsonResponse::<models::Stack>::build()
-                .not_found(format!("Object not found {}", id));
+            return Err(JsonResponse::<models::Stack>::build()
+                .not_found(format!("Object not found {}", id)));
         }
     };
 
@@ -72,7 +72,7 @@ pub async fn update(
         let errors = form.validate().unwrap_err();
         let err_msg = format!("Invalid data received {:?}", &errors.to_string());
         tracing::debug!(err_msg);
-        return JsonResponse::build().bad_request(errors.to_string());
+        return Err(JsonResponse::<models::Stack>::build().bad_request(errors.to_string()));
     }
 
     let body: Value = match serde_json::to_value::<StackForm>(form.into_inner()) {
@@ -106,11 +106,11 @@ pub async fn update(
                 "req_id: {} stack details have been saved to database",
                 request_id
             );
-            return JsonResponse::build().set_id(id).ok("OK");
+            return Ok(JsonResponse::<models::Stack>::build().set_id(id).ok("OK"));
         }
         Err(e) => {
             tracing::error!("req_id: {} Failed to execute query: {:?}", request_id, e);
-            return JsonResponse::build().bad_request("Internal Server Error");
+            return Err(JsonResponse::<models::Stack>::build().bad_request("Internal Server Error"));
         }
     };
 }
diff --git a/src/routes/test/deploy.rs b/src/routes/test/deploy.rs
index 51cc2014..4f36a3ae 100644
--- a/src/routes/test/deploy.rs
+++ b/src/routes/test/deploy.rs
@@ -13,5 +13,5 @@ struct DeployResponse {
 #[tracing::instrument(name = "Test deploy.")]
 #[post("/deploy")]
 pub async fn handler(client: web::ReqData<Arc<Client>>) -> Result<impl Responder> {
-    JsonResponse::build().set_item(client.into_inner()).ok("success")
+    Ok(JsonResponse::build().set_item(client.into_inner()).ok("success"))
 }

From 65d6f431f125a6c7c07aa0f2907973c10133623d Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Tue, 12 Dec 2023 18:02:07 +0200
Subject: [PATCH 059/284] issue-auth json. unauthorized response

---
 src/helpers/json.rs         | 7 +++++++
 src/middleware/trydirect.rs | 6 +++---
 2 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/src/helpers/json.rs b/src/helpers/json.rs
index 629f65e5..e92c1f7b 100644
--- a/src/helpers/json.rs
+++ b/src/helpers/json.rs
@@ -85,6 +85,13 @@ where
         ErrorInternalServerError(self.set_msg(msg).to_string())
     }
 
+    pub(crate) fn unauthorized<I: Into<String>>(
+        self,
+        msg: I,
+    ) -> Error {
+        ErrorUnauthorized(self.set_msg(msg).to_string())
+    }
+ 
     pub(crate) fn conflict<I: Into<String>>(self, msg: I) -> Error {
         ErrorConflict(self.set_msg(msg).to_string())
     }
diff --git a/src/middleware/trydirect.rs b/src/middleware/trydirect.rs
index bbe5e4bf..823680a7 100644
--- a/src/middleware/trydirect.rs
+++ b/src/middleware/trydirect.rs
@@ -1,5 +1,5 @@
 use crate::{models, configuration::Settings, forms::user::UserForm, helpers::JsonResponse};
-use actix_web::{web, dev::ServiceRequest, Error, HttpMessage, error::{ErrorInternalServerError, ErrorUnauthorized}};
+use actix_web::{web, dev::ServiceRequest, Error, HttpMessage};
 use actix_web_httpauth::extractors::bearer::BearerAuth;
 use futures::future::{FutureExt};
 use reqwest::header::{ACCEPT, CONTENT_TYPE};
@@ -11,12 +11,12 @@ pub async fn bearer_guard( req: ServiceRequest, credentials: BearerAuth) -> Resu
     let token = credentials.token();
     let user = fetch_user(settings.auth_url.as_str(), token).await;
     if let Err(err) = user {
-        return Err((ErrorUnauthorized(JsonResponse::<i32>::build().set_msg(err).to_string()), req));
+        return Err((JsonResponse::<i32>::build().unauthorized(err), req));
     }
 
     let user = user.unwrap();
     if req.extensions_mut().insert(Arc::new(user)).is_some() {
-        return Err((ErrorUnauthorized(JsonResponse::<i32>::build().set_msg("user already logged").to_string()), req));
+        return Err((JsonResponse::<i32>::build().unauthorized("user already logged"), req));
     }
 
     Ok(req)

From 6630ab74fb16fb5735d877e511b0f8439c74cbd3 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Tue, 12 Dec 2023 18:07:42 +0200
Subject: [PATCH 060/284] issue-auth src/middleware/trydirect.rs

---
 src/helpers/json.rs         |  2 +-
 src/middleware/trydirect.rs | 12 +++++++-----
 2 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/src/helpers/json.rs b/src/helpers/json.rs
index e92c1f7b..952d7680 100644
--- a/src/helpers/json.rs
+++ b/src/helpers/json.rs
@@ -1,4 +1,4 @@
-use actix_web::error::{ErrorBadRequest, ErrorConflict, ErrorInternalServerError, ErrorNotFound};
+use actix_web::error::{ErrorBadRequest, ErrorConflict, ErrorInternalServerError, ErrorNotFound, ErrorUnauthorized};
 use actix_web::web::Json;
 use actix_web::Error;
 use serde_derive::Serialize;
diff --git a/src/middleware/trydirect.rs b/src/middleware/trydirect.rs
index 823680a7..d370a47a 100644
--- a/src/middleware/trydirect.rs
+++ b/src/middleware/trydirect.rs
@@ -9,12 +9,14 @@ use std::sync::Arc;
 pub async fn bearer_guard( req: ServiceRequest, credentials: BearerAuth) -> Result<ServiceRequest, (Error, ServiceRequest)> {
     let settings = req.app_data::<web::Data<Settings>>().unwrap();
     let token = credentials.token();
-    let user = fetch_user(settings.auth_url.as_str(), token).await;
-    if let Err(err) = user {
-        return Err((JsonResponse::<i32>::build().unauthorized(err), req));
-    }
+    let user = match fetch_user(settings.auth_url.as_str(), token)
+        .await {
+            Ok(user) => user,
+            Err(err) => {
+                return Err((JsonResponse::<i32>::build().unauthorized(err), req));
+            }
+    };
 
-    let user = user.unwrap();
     if req.extensions_mut().insert(Arc::new(user)).is_some() {
         return Err((JsonResponse::<i32>::build().unauthorized("user already logged"), req));
     }

From 899b8f8a1f4cbaceb92c0e2e8ed9bcb671f04cbf Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Tue, 12 Dec 2023 18:11:02 +0200
Subject: [PATCH 061/284] issue-auth src/routes/client/add.rs

---
 src/middleware/trydirect.rs | 11 +++++------
 src/routes/client/add.rs    |  8 ++++----
 2 files changed, 9 insertions(+), 10 deletions(-)

diff --git a/src/middleware/trydirect.rs b/src/middleware/trydirect.rs
index d370a47a..e40f89b1 100644
--- a/src/middleware/trydirect.rs
+++ b/src/middleware/trydirect.rs
@@ -9,12 +9,11 @@ use std::sync::Arc;
 pub async fn bearer_guard( req: ServiceRequest, credentials: BearerAuth) -> Result<ServiceRequest, (Error, ServiceRequest)> {
     let settings = req.app_data::<web::Data<Settings>>().unwrap();
     let token = credentials.token();
-    let user = match fetch_user(settings.auth_url.as_str(), token)
-        .await {
-            Ok(user) => user,
-            Err(err) => {
-                return Err((JsonResponse::<i32>::build().unauthorized(err), req));
-            }
+    let user = match fetch_user(settings.auth_url.as_str(), token).await {
+        Ok(user) => user,
+        Err(err) => {
+            return Err((JsonResponse::<i32>::build().unauthorized(err), req));
+        }
     };
 
     if req.extensions_mut().insert(Arc::new(user)).is_some() {
diff --git a/src/routes/client/add.rs b/src/routes/client/add.rs
index f922718d..74040e53 100644
--- a/src/routes/client/add.rs
+++ b/src/routes/client/add.rs
@@ -14,10 +14,10 @@ pub async fn add_handler(
     settings: web::Data<Settings>,
     pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
-    match add_handler_inner(&user.id, settings, pool).await {
-        Ok(client) => Ok(JsonResponse::build().set_item(client).ok("Ok")),
-        Err(msg) => Err(JsonResponse::<models::Client>::build().bad_request(msg)),
-    }
+    add_handler_inner(&user.id, settings, pool)
+        .await
+        .map(|client| JsonResponse::build().set_item(client).ok("Ok"))
+        .map_err(|err| JsonResponse::<models::Client>::build().bad_request(err))
 }
 
 pub async fn add_handler_inner(

From 892ff9def9e09de4ee68501d799398af47132597 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Tue, 12 Dec 2023 18:22:49 +0200
Subject: [PATCH 062/284] issue-auth enable client

---
 src/routes/client/enable.rs | 31 ++++++++++++++++---------------
 1 file changed, 16 insertions(+), 15 deletions(-)

diff --git a/src/routes/client/enable.rs b/src/routes/client/enable.rs
index 59bde80a..527e6dbd 100644
--- a/src/routes/client/enable.rs
+++ b/src/routes/client/enable.rs
@@ -15,23 +15,24 @@ pub async fn enable_handler(
     pool: web::Data<PgPool>,
     path: web::Path<(i32,)>,
 ) -> Result<impl Responder> {
-    match async {
-        let client_id = path.0;
-        let mut client = db_fetch_client_by_id(pool.get_ref(), client_id).await?; 
-        if client.secret.is_some() {
-            return Err("client is already active".to_string());
-        }
+    let client_id = path.0;
+    let mut client = db_fetch_client_by_id(pool.get_ref(), client_id)
+        .await
+        .map_err(|err| JsonResponse::<models::Client>::build().bad_request(err))?; 
 
-        client.secret = Some(client::generate_secret(pool.get_ref(), 255).await?);
-        db_update_client(pool.get_ref(), client).await
-    }.await {
-        Ok(client) => {
-            Ok(JsonResponse::build().set_item(client).ok("success"))
-        }
-        Err(msg) => {
-            Err(JsonResponse::<models::Client>::build().bad_request(msg))
-        }
+    if client.secret.is_some() {
+        return Err(JsonResponse::<models::Client>::build().bad_request("client is already active"));
     }
+
+    client.secret = client::generate_secret(pool.get_ref(), 255)
+        .await
+        .map(|secret| Some(secret))
+        .map_err(|err| JsonResponse::<models::Client>::build().bad_request(err))?;
+
+    db_update_client(pool.get_ref(), client)
+        .await
+        .map(|client| JsonResponse::build().set_item(client).ok("success"))
+        .map_err(|err| JsonResponse::<models::Client>::build().bad_request(err))
 }
 
 async fn db_fetch_client_by_id(pool: &PgPool, id: i32) -> Result<models::Client, String> {

From 9989b118cc4f2a66a85b992e3b154522a47afa51 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Wed, 13 Dec 2023 16:52:17 +0200
Subject: [PATCH 063/284] issue-auth client logic refactor

---
 src/db/client/mod.rs         | 99 ++++++++++++++++++++++++++++++++++++
 src/db/mod.rs                |  1 +
 src/lib.rs                   |  1 +
 src/routes/client/add.rs     | 50 ++----------------
 src/routes/client/disable.rs | 64 +++--------------------
 src/routes/client/enable.rs  | 60 ++--------------------
 6 files changed, 115 insertions(+), 160 deletions(-)
 create mode 100644 src/db/client/mod.rs
 create mode 100644 src/db/mod.rs

diff --git a/src/db/client/mod.rs b/src/db/client/mod.rs
new file mode 100644
index 00000000..b006d97b
--- /dev/null
+++ b/src/db/client/mod.rs
@@ -0,0 +1,99 @@
+use sqlx::PgPool;
+use crate::models;
+use tracing::Instrument;
+
+pub async fn update(pool: &PgPool, client: models::Client) -> Result<models::Client, String> {
+    let query_span = tracing::info_span!("Updating client into the database");
+    sqlx::query!(
+        r#"
+        UPDATE client SET 
+            secret=$1,
+            updated_at=NOW() at time zone 'utc'
+        WHERE id = $2
+        "#,
+        client.secret,
+        client.id
+    )
+    .execute(pool)
+    .instrument(query_span)
+    .await
+    .map(|_|{
+        tracing::info!("Client {} have been saved to database", client.id);
+        client
+    })
+    .map_err(|err| {
+        tracing::error!("Failed to execute query: {:?}", err);
+        "".to_string()
+    })
+}
+
+pub async fn fetch(pool: &PgPool, id: i32) -> Result<models::Client, String> {
+    let query_span = tracing::info_span!("Fetching the client by ID");
+    sqlx::query_as!(
+        models::Client,
+        r#"
+        SELECT
+           id, user_id, secret 
+        FROM client c
+        WHERE c.id = $1
+        "#,
+        id,
+    )
+    .fetch_one(pool)
+    .instrument(query_span)
+    .await
+    .map_err(|e| {
+        match e {
+            sqlx::Error::RowNotFound => "client not found".to_string(),
+            s => {
+                tracing::error!("Failed to execute fetch query: {:?}", s);
+                "".to_string()
+            }
+        }
+    })
+}
+
+pub async fn count_by_user(pool: &PgPool , user_id: &String) -> Result<i64, String> {
+    let query_span = tracing::info_span!("Counting the user's clients");
+
+    sqlx::query!(
+        r#"
+        SELECT
+            count(*) as client_count
+        FROM client c 
+        WHERE c.user_id = $1
+        "#,
+        user_id.clone(),
+    )
+    .fetch_one(pool)
+    .instrument(query_span)
+    .await
+    .map(|result| {result.client_count.unwrap()})
+    .map_err(|err| {
+        tracing::error!("Failed to execute query: {:?}", err);
+        "Internal Server Error".to_string()
+    })
+}
+
+pub async fn  insert(pool: &PgPool, mut client: models::Client) -> Result<models::Client, String> {
+    let query_span = tracing::info_span!("Saving new client into the database");
+    sqlx::query!(
+        r#"
+        INSERT INTO client (user_id, secret, created_at, updated_at)
+        VALUES ($1, $2, NOW() at time zone 'utc', NOW() at time zone 'utc')
+        RETURNING id
+        "#,
+        client.user_id.clone(),
+        client.secret,
+    )
+    .fetch_one(pool)
+    .instrument(query_span)
+    .await
+    .map(move |result| {
+        client.id = result.id;
+        client
+    }).map_err(|e| {
+        tracing::error!("Failed to execute query: {:?}", e);
+        "Failed to insert".to_string()
+    })
+}
diff --git a/src/db/mod.rs b/src/db/mod.rs
new file mode 100644
index 00000000..b9babe5b
--- /dev/null
+++ b/src/db/mod.rs
@@ -0,0 +1 @@
+pub mod client;
diff --git a/src/lib.rs b/src/lib.rs
index 37e4fae9..3c6af7cc 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,5 +1,6 @@
 pub mod configuration;
 pub mod console;
+pub mod db;
 pub mod forms;
 pub mod helpers;
 mod middleware;
diff --git a/src/routes/client/add.rs b/src/routes/client/add.rs
index 74040e53..b3d90d94 100644
--- a/src/routes/client/add.rs
+++ b/src/routes/client/add.rs
@@ -2,6 +2,7 @@ use crate::configuration::Settings;
 use crate::helpers::client;
 use crate::helpers::JsonResponse;
 use crate::models;
+use crate::db;
 use actix_web::{post, web, Responder, Result};
 use sqlx::PgPool;
 use tracing::Instrument;
@@ -25,58 +26,13 @@ pub async fn add_handler_inner(
     settings: web::Data<Settings>,
     pool: web::Data<PgPool>,
 ) -> Result<models::Client, String> {
-    let client_count = db_count_clients_by_user(pool.get_ref(), user_id).await?;
+    let client_count = db::client::count_by_user(pool.get_ref(), user_id).await?;
     if client_count >= settings.max_clients_number {
         return Err("Too many clients created".to_string());
     }
 
     let client = create_client(pool.get_ref(), user_id).await?;
-    db_insert_client(pool.get_ref(), client).await
-}
-
-async fn db_count_clients_by_user(pool: &PgPool , user_id: &String) -> Result<i64, String> {
-    let query_span = tracing::info_span!("Counting the user's clients");
-
-    sqlx::query!(
-        r#"
-        SELECT
-            count(*) as client_count
-        FROM client c 
-        WHERE c.user_id = $1
-        "#,
-        user_id.clone(),
-    )
-    .fetch_one(pool)
-    .instrument(query_span)
-    .await
-    .map(|result| {result.client_count.unwrap()})
-    .map_err(|err| {
-        tracing::error!("Failed to execute query: {:?}", err);
-        "Internal Server Error".to_string()
-    })
-}
-
-async fn  db_insert_client(pool: &PgPool, mut client: models::Client) -> Result<models::Client, String> {
-    let query_span = tracing::info_span!("Saving new client into the database");
-    sqlx::query!(
-        r#"
-        INSERT INTO client (user_id, secret, created_at, updated_at)
-        VALUES ($1, $2, NOW() at time zone 'utc', NOW() at time zone 'utc')
-        RETURNING id
-        "#,
-        client.user_id.clone(),
-        client.secret,
-    )
-    .fetch_one(pool)
-    .instrument(query_span)
-    .await
-    .map(move |result| {
-        client.id = result.id;
-        client
-    }).map_err(|e| {
-        tracing::error!("Failed to execute query: {:?}", e);
-        "Failed to insert".to_string()
-    })
+    db::client::insert(pool.get_ref(), client).await
 }
 
 async fn create_client(pool: &PgPool, user_id: &String) -> Result<models::Client, String> {
diff --git a/src/routes/client/disable.rs b/src/routes/client/disable.rs
index c5d2ac32..2159febf 100644
--- a/src/routes/client/disable.rs
+++ b/src/routes/client/disable.rs
@@ -1,6 +1,7 @@
 use crate::configuration::Settings;
 use crate::helpers::JsonResponse;
 use crate::models;
+use crate::db;
 use actix_web::{put, web, Responder, Result};
 use sqlx::PgPool;
 use tracing::Instrument;
@@ -15,69 +16,16 @@ pub async fn disable_handler(
     path: web::Path<(i32,)>,
 ) -> Result<impl Responder> {
         let client_id = path.0;
-        let mut client = db_fetch_client_by_id(pool.get_ref(), client_id)
+        let mut client = db::client::fetch(pool.get_ref(), client_id)
             .await
-            .map_err(|msg| JsonResponse::<models::Client>::build().not_found(msg))
-            ?; 
+            .map_err(|msg| JsonResponse::<models::Client>::build().not_found(msg))?; 
         if client.secret.is_none() {
             return Err(JsonResponse::<models::Client>::build().bad_request("client is not active"));
         }
 
         client.secret = None;
-        let client = db_update_client(pool.get_ref(), client)
+        db::client::update(pool.get_ref(), client)
             .await
-            .map_err(|msg| JsonResponse::<models::Client>::build().bad_request(msg))?;
-
-        Ok(JsonResponse::build().set_item(client).ok("success"))
-}
-
-async fn db_fetch_client_by_id(pool: &PgPool, id: i32) -> Result<models::Client, String> {
-    let query_span = tracing::info_span!("Fetching the client by ID");
-    sqlx::query_as!(
-        models::Client,
-        r#"
-        SELECT
-           id, user_id, secret 
-        FROM client c
-        WHERE c.id = $1
-        "#,
-        id,
-    )
-    .fetch_one(pool)
-    .instrument(query_span)
-    .await
-    .map_err(|e| {
-        match e {
-            sqlx::Error::RowNotFound => "client not found".to_string(),
-            s => {
-                tracing::error!("Failed to execute fetch query: {:?}", s);
-                "".to_string()
-            }
-        }
-    })
-}
-
-async fn db_update_client(pool: &PgPool, client: models::Client) -> Result<models::Client , String> {
-    let query_span = tracing::info_span!("Updating client into the database");
-    sqlx::query!(
-        r#"
-        UPDATE client SET 
-            secret=$1,
-            updated_at=NOW() at time zone 'utc'
-        WHERE id = $2
-        "#,
-        client.secret,
-        client.id
-    )
-    .execute(pool)
-    .instrument(query_span)
-    .await
-    .map(|_|{
-        tracing::info!("Client {} have been saved to database", client.id);
-        client
-    })
-    .map_err(|err| {
-        tracing::error!("Failed to execute query: {:?}", err);
-        "".to_string()
-    })
+            .map(|client| JsonResponse::build().set_item(client).ok("success"))
+            .map_err(|msg| JsonResponse::<models::Client>::build().bad_request(msg))
 }
diff --git a/src/routes/client/enable.rs b/src/routes/client/enable.rs
index 527e6dbd..6ed95d26 100644
--- a/src/routes/client/enable.rs
+++ b/src/routes/client/enable.rs
@@ -2,9 +2,10 @@ use crate::configuration::Settings;
 use crate::helpers::client;
 use crate::helpers::JsonResponse;
 use crate::models;
+use crate::db;
 use actix_web::{put, web, Responder, Result};
-use sqlx::PgPool;
 use tracing::Instrument;
+use sqlx::PgPool;
 use std::sync::Arc;
 
 #[tracing::instrument(name = "Enable client.")]
@@ -16,9 +17,9 @@ pub async fn enable_handler(
     path: web::Path<(i32,)>,
 ) -> Result<impl Responder> {
     let client_id = path.0;
-    let mut client = db_fetch_client_by_id(pool.get_ref(), client_id)
+    let mut client = db::client::fetch(pool.get_ref(), client_id)
         .await
-        .map_err(|err| JsonResponse::<models::Client>::build().bad_request(err))?; 
+        .map_err(|msg| JsonResponse::<models::Client>::build().not_found(msg))?; 
 
     if client.secret.is_some() {
         return Err(JsonResponse::<models::Client>::build().bad_request("client is already active"));
@@ -29,59 +30,8 @@ pub async fn enable_handler(
         .map(|secret| Some(secret))
         .map_err(|err| JsonResponse::<models::Client>::build().bad_request(err))?;
 
-    db_update_client(pool.get_ref(), client)
+    db::client::update(pool.get_ref(), client)
         .await
         .map(|client| JsonResponse::build().set_item(client).ok("success"))
         .map_err(|err| JsonResponse::<models::Client>::build().bad_request(err))
 }
-
-async fn db_fetch_client_by_id(pool: &PgPool, id: i32) -> Result<models::Client, String> {
-    let query_span = tracing::info_span!("Fetching the client by ID");
-    sqlx::query_as!(
-        models::Client,
-        r#"
-        SELECT
-           id, user_id, secret 
-        FROM client c
-        WHERE c.id = $1
-        "#,
-        id,
-    )
-    .fetch_one(pool)
-    .instrument(query_span)
-    .await
-    .map_err(|e| {
-        match e {
-            sqlx::Error::RowNotFound => "client not found".to_string(),
-            s => {
-                tracing::error!("Failed to execute fetch query: {:?}", s);
-                "".to_string()
-            }
-        }
-    })
-}
-
-async fn db_update_client(pool: &PgPool, client: models::Client) -> Result<models::Client, String> {
-    let query_span = tracing::info_span!("Updating client into the database");
-    sqlx::query!(
-        r#"
-        UPDATE client SET 
-            secret=$1,
-            updated_at=NOW() at time zone 'utc'
-        WHERE id = $2
-        "#,
-        client.secret,
-        client.id
-    )
-    .execute(pool)
-    .instrument(query_span)
-    .await
-    .map(|_|{
-        tracing::info!("Client {} have been saved to database", client.id);
-        client
-    })
-    .map_err(|err| {
-        tracing::error!("Failed to execute query: {:?}", err);
-        "".to_string()
-    })
-}

From ee200c45499c6eb38a3120020be8bdf61194360b Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Wed, 13 Dec 2023 17:02:33 +0200
Subject: [PATCH 064/284] issue-auth src/routes/client/update.rs

---
 src/routes/client/update.rs | 64 +++++++++----------------------------
 1 file changed, 15 insertions(+), 49 deletions(-)

diff --git a/src/routes/client/update.rs b/src/routes/client/update.rs
index 0fff40eb..4a0fe8e8 100644
--- a/src/routes/client/update.rs
+++ b/src/routes/client/update.rs
@@ -1,10 +1,12 @@
 use crate::helpers::client;
 use crate::models;
+use crate::db;
 use crate::{configuration::Settings, helpers::JsonResponse};
 use actix_web::{error::ErrorBadRequest, put, web, Responder, Result};
 use sqlx::PgPool;
 use std::sync::Arc;
 use tracing::Instrument;
+use futures::TryFutureExt;
 
 #[tracing::instrument(name = "Update client.")]
 #[put("/{id}")]
@@ -15,59 +17,23 @@ pub async fn update_handler(
     path: web::Path<(i32,)>,
 ) -> Result<impl Responder> {
     let client_id = path.0;
-    let query_span = tracing::info_span!("Fetching the client by ID");
-    let mut client: models::Client = match sqlx::query_as!(
-        models::Client,
-        r#"
-        SELECT
-           id, user_id, secret 
-        FROM client c
-        WHERE c.id = $1
-        "#,
-        client_id,
-    )
-    .fetch_one(pool.get_ref())
-    .instrument(query_span)
-    .await
-    {
-        Ok(client) if client.secret.is_some() => Ok(client),
-        Ok(_client) => Err("client is not active"),
-        Err(sqlx::Error::RowNotFound) => Err("the client is not found"),
-        Err(e) => {
-            tracing::error!("Failed to execute fetch query: {:?}", e);
-
-            Err("")
-        }
+    let mut client = db::client::fetch(pool.get_ref(), client_id)
+        .await
+        .map_err(|msg| JsonResponse::<models::Client>::build().not_found(msg))?; 
+    if client.secret.is_none() {
+        return Err(JsonResponse::<models::Client>::build().bad_request("client is not active"));
     }
-    .map_err(|s| ErrorBadRequest(JsonResponse::<models::Client>::build().set_msg(s).to_string()))?;
 
     client.secret = client::generate_secret(pool.get_ref(), 255)
         .await
         .map(|s| Some(s))
-        .map_err(|s| ErrorBadRequest(JsonResponse::<models::Client>::build().set_msg(s).to_string()))?;
+        .map_err(|msg| JsonResponse::<models::Client>::build().bad_request(msg))?;
 
-    let query_span = tracing::info_span!("Updating client into the database");
-    match sqlx::query!(
-        r#"
-        UPDATE client SET 
-            secret=$1,
-            updated_at=NOW() at time zone 'utc'
-        WHERE id = $2
-        "#,
-        client.secret,
-        client.id
-    )
-    .execute(pool.get_ref())
-    .instrument(query_span)
-    .await
-    {
-        Ok(_) => {
-            tracing::info!("Client {} have been saved to database", client.id);
-            Ok(JsonResponse::<models::Client>::build().set_item(client).ok("success"))
-        }
-        Err(e) => {
-            tracing::error!("Failed to execute query: {:?}", e);
-            Err(JsonResponse::<models::Client>::build().internal_server_error(""))
-        }
-    }
+    db::client::update(pool.get_ref(), client)
+        .await
+        .map(|client| JsonResponse::<models::Client>::build().set_item(client).ok("success"))
+        .map_err(|err| {
+            tracing::error!("Failed to execute query: {:?}", err);
+            JsonResponse::<models::Client>::build().internal_server_error("")
+        })
 }

From f1cba7e5c8cd48aa57195e1e3b51b84c2a0243e4 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Wed, 13 Dec 2023 17:06:07 +0200
Subject: [PATCH 065/284] issue-auth

---
 src/middleware/client.rs    | 1 -
 src/routes/client/update.rs | 2 +-
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/middleware/client.rs b/src/middleware/client.rs
index 27aa97b7..60bce270 100644
--- a/src/middleware/client.rs
+++ b/src/middleware/client.rs
@@ -1,7 +1,6 @@
 use crate::helpers::JsonResponse;
 use crate::models::Client;
 use actix_http::header::CONTENT_LENGTH;
-use actix_web::error::{ErrorForbidden, ErrorInternalServerError, ErrorNotFound, PayloadError};
 use actix_web::web::BytesMut;
 use actix_web::HttpMessage;
 use futures::future::{FutureExt, LocalBoxFuture};
diff --git a/src/routes/client/update.rs b/src/routes/client/update.rs
index 4a0fe8e8..63b02a17 100644
--- a/src/routes/client/update.rs
+++ b/src/routes/client/update.rs
@@ -2,7 +2,7 @@ use crate::helpers::client;
 use crate::models;
 use crate::db;
 use crate::{configuration::Settings, helpers::JsonResponse};
-use actix_web::{error::ErrorBadRequest, put, web, Responder, Result};
+use actix_web::{put, web, Responder, Result};
 use sqlx::PgPool;
 use std::sync::Arc;
 use tracing::Instrument;

From 3ba9b904ef732bdf0db4da11d3fe545c870597a8 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Wed, 13 Dec 2023 17:08:39 +0200
Subject: [PATCH 066/284] issue-auth renamed client/mod.rs into client.rs

---
 src/db/{client/mod.rs => client.rs} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename src/db/{client/mod.rs => client.rs} (100%)

diff --git a/src/db/client/mod.rs b/src/db/client.rs
similarity index 100%
rename from src/db/client/mod.rs
rename to src/db/client.rs

From 33d9cd8788a6edf5021c000c1903bba342170d52 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Wed, 13 Dec 2023 18:09:43 +0200
Subject: [PATCH 067/284] issue-auth working on src/routes/rating/add.rs

---
 src/db/client.rs         |  3 +-
 src/db/mod.rs            |  2 +
 src/db/product.rs        | 24 ++++++++++
 src/db/rating.rs         | 55 +++++++++++++++++++++++
 src/routes/rating/add.rs | 97 +++++++++-------------------------------
 5 files changed, 105 insertions(+), 76 deletions(-)
 create mode 100644 src/db/product.rs
 create mode 100644 src/db/rating.rs

diff --git a/src/db/client.rs b/src/db/client.rs
index b006d97b..750eefd2 100644
--- a/src/db/client.rs
+++ b/src/db/client.rs
@@ -92,7 +92,8 @@ pub async fn  insert(pool: &PgPool, mut client: models::Client) -> Result<models
     .map(move |result| {
         client.id = result.id;
         client
-    }).map_err(|e| {
+    })
+    .map_err(|e| {
         tracing::error!("Failed to execute query: {:?}", e);
         "Failed to insert".to_string()
     })
diff --git a/src/db/mod.rs b/src/db/mod.rs
index b9babe5b..0aec0371 100644
--- a/src/db/mod.rs
+++ b/src/db/mod.rs
@@ -1 +1,3 @@
 pub mod client;
+pub mod product;
+pub mod rating;
diff --git a/src/db/product.rs b/src/db/product.rs
new file mode 100644
index 00000000..d80092cb
--- /dev/null
+++ b/src/db/product.rs
@@ -0,0 +1,24 @@
+use sqlx::PgPool;
+use crate::models;
+use tracing::Instrument;
+
+pub async fn fetch_by_obj(pg_pool: &PgPool, obj_id: i32) -> Result<models::Product, String> {
+    let query_span = tracing::info_span!("Check product existence by id.");
+    sqlx::query_as!(
+        models::Product,
+        r"SELECT * FROM product WHERE obj_id = $1",
+        obj_id
+    )
+    .fetch_one(pg_pool)
+    .instrument(query_span)
+    .await
+    .map_err(|e|  {
+        match e {
+            sqlx::Error::RowNotFound => "client not found".to_string(),
+            s => {
+                tracing::error!("Failed to execute fetch query: {:?}", s);
+                "".to_string()
+            }
+        }
+    })
+}
diff --git a/src/db/rating.rs b/src/db/rating.rs
new file mode 100644
index 00000000..7d6a12eb
--- /dev/null
+++ b/src/db/rating.rs
@@ -0,0 +1,55 @@
+use sqlx::PgPool;
+use crate::models;
+use tracing::Instrument;
+
+pub async fn fetch_by_obj_and_user_and_category(pool: &PgPool, obj_id: i32, user_id: String, category: models::RateCategory) -> Result<models::Rating, String> {
+    let query_span = tracing::info_span!("Search for existing vote.");
+    sqlx::query_as!(
+        models::Rating,
+        r"SELECT * FROM rating where user_id=$1 AND obj_id=$2 AND category=$3 LIMIT 1",
+        user_id,
+        obj_id,
+        "ok" //todo put there the category
+        //category.into() //todo
+    )
+    .fetch_one(pool)
+    .instrument(query_span)
+    .await
+    .map_err(|e| {
+        match e {
+            sqlx::Error::RowNotFound => "client not found".to_string(),
+            s => {
+                tracing::error!("Failed to execute fetch query: {:?}", s);
+                "".to_string()
+            }
+        }
+    })
+}
+
+pub async fn insert(pool: &PgPool, mut rating: models::Rating) -> Result<models::Rating, String> {
+    let query_span = tracing::info_span!("Saving new rating details into the database");
+    sqlx::query!(
+        r#"
+        INSERT INTO rating (user_id, obj_id, category, comment, hidden, rate, created_at, updated_at)
+        VALUES ($1, $2, $3, $4, $5, $6, NOW() at time zone 'utc', NOW() at time zone 'utc')
+        RETURNING id
+        "#,
+        rating.user_id,
+        rating.obj_id,
+        rating.category,
+        rating.comment,
+        rating.hidden,
+        rating.rate
+    )
+    .fetch_one(pool)
+    .instrument(query_span)
+    .await
+    .map(move |result| {
+        rating.id = result.id;
+        rating
+    })
+    .map_err(|e| {
+        tracing::error!("Failed to execute query: {:?}", e);
+        "Failed to insert".to_string()
+    })
+}
diff --git a/src/routes/rating/add.rs b/src/routes/rating/add.rs
index 5e2e8744..54a1ada5 100644
--- a/src/routes/rating/add.rs
+++ b/src/routes/rating/add.rs
@@ -1,12 +1,12 @@
 use crate::forms;
 use crate::helpers::JsonResponse;
 use crate::models;
-use crate::models::user::User;
-use crate::models::RateCategory;
+use crate::db;
 use actix_web::{post, web, Responder, Result};
 use sqlx::PgPool;
 use tracing::Instrument;
 use std::sync::Arc;
+use futures::TryFutureExt;
 
 // workflow
 // add, update, list, get(user_id), ACL,
@@ -16,86 +16,33 @@ use std::sync::Arc;
 #[tracing::instrument(name = "Add rating.")]
 #[post("")]
 pub async fn add_handler(
-    user: web::ReqData<Arc<User>>,
+    user: web::ReqData<Arc<models::User>>,
     form: web::Json<forms::Rating>,
-    pool: web::Data<PgPool>,
+    pg_pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
-    let query_span = tracing::info_span!("Check product existence by id.");
-    match sqlx::query_as!(
-        models::Product,
-        r"SELECT * FROM product WHERE obj_id = $1",
-        form.obj_id
-    )
-    .fetch_one(pool.get_ref())
-    .instrument(query_span)
-    .await
-    {
-        Ok(product) => {
-            tracing::info!("Found product: {:?}", product.obj_id);
-        }
-        Err(e) => {
-            tracing::error!("Failed to fetch product: {:?}, error: {:?}", form.obj_id, e);
-            return Err(JsonResponse::<models::Rating>::build().bad_request("Object not found"));
-        }
-    };
+    let _product = db::product::fetch_by_obj(pg_pool.get_ref(), form.obj_id)
+        .await
+        .map_err(|msg| JsonResponse::<models::Rating>::build().not_found(msg))?; 
 
-    let query_span = tracing::info_span!("Search for existing vote.");
-    match sqlx::query!(
-        r"SELECT id FROM rating where user_id=$1 AND obj_id=$2 AND category=$3 LIMIT 1",
-        user.id,
-        form.obj_id,
-        form.category as RateCategory
-    )
-    .fetch_one(pool.get_ref())
-    .instrument(query_span)
-    .await
-    {
+    match db::rating::fetch_by_obj_and_user_and_category(pg_pool.get_ref(), form.obj_id, user.id.clone(), form.category).await {
         Ok(record) => {
-            tracing::info!(
-                "rating exists: {:?}, user: {}, product: {}, category: {:?}",
-                record.id,
-                user.id,
-                form.obj_id,
-                form.category
-            );
             return Err(JsonResponse::<models::Rating>::build().conflict("Already rated"));
         }
-        Err(sqlx::Error::RowNotFound) => {}
-        Err(e) => {
-            tracing::error!("Failed to fetch rating, error: {:?}", e);
-            return Err(JsonResponse::<models::Rating>::build().internal_server_error("Internal Server Error"));
-        }
+        Err(_e) => {}
     }
 
-    let query_span = tracing::info_span!("Saving new rating details into the database");
-    // Insert rating
-    match sqlx::query!(
-        r#"
-        INSERT INTO rating (user_id, obj_id, category, comment, hidden,rate,
-        created_at,
-        updated_at)
-        VALUES ($1, $2, $3, $4, $5, $6, NOW() at time zone 'utc', NOW() at time zone 'utc')
-        RETURNING id
-        "#,
-        user.id,
-        form.obj_id,
-        form.category as models::RateCategory,
-        form.comment,
-        false,
-        form.rate
-    )
-    .fetch_one(pool.get_ref())
-    .instrument(query_span)
-    .await
-    {
-        Ok(result) => {
-            tracing::info!("New rating {} have been saved to database", result.id);
+    let mut rating = models::Rating::default(); //todo into trait
+    rating.user_id = user.id.clone();
+    rating.obj_id = form.obj_id;
+    rating.category = form.category.into(); //todo change the type of category field to the
+                                            //RateCategory
+    rating.comment = form.comment.clone(); //todo how to do it correctly?
+    rating.hidden = Some(false); //todo add to form
+    rating.rate = Some(form.rate);
 
-            Ok(JsonResponse::<models::Rating>::build().set_id(result.id).ok("Saved"))
-        }
-        Err(e) => {
-            tracing::error!("Failed to execute query: {:?}", e);
-            Err(JsonResponse::<models::Rating>::build().internal_server_error("Failed to insert"))
-        }
-    }
+    db::rating::insert(pg_pool.get_ref(), rating)
+        .await
+        .map(|rating| JsonResponse::build().set_item(rating).ok("success"))
+        .map_err(|err| JsonResponse::<models::Rating>::build().internal_server_error("Failed to insert"))
+    //todo. verify that created_at and updated_at are also added to the response
 }

From ccd134042e2554a8bbf0da4a06f6a71ac18b0a16 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Thu, 14 Dec 2023 17:46:08 +0200
Subject: [PATCH 068/284] issue-auth message product

---
 src/db/product.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/db/product.rs b/src/db/product.rs
index d80092cb..d409d57b 100644
--- a/src/db/product.rs
+++ b/src/db/product.rs
@@ -14,7 +14,7 @@ pub async fn fetch_by_obj(pg_pool: &PgPool, obj_id: i32) -> Result<models::Produ
     .await
     .map_err(|e|  {
         match e {
-            sqlx::Error::RowNotFound => "client not found".to_string(),
+            sqlx::Error::RowNotFound => "product not found".to_string(),
             s => {
                 tracing::error!("Failed to execute fetch query: {:?}", s);
                 "".to_string()

From c8fca0b0de5d81aa71c70545a80f45b73e7f7fb1 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Thu, 14 Dec 2023 18:02:45 +0200
Subject: [PATCH 069/284] issue-auth src/routes/rating/add.rs

---
 src/forms/rating.rs      | 13 +++++++++++++
 src/models/rating.rs     |  1 +
 src/routes/rating/add.rs |  9 +--------
 3 files changed, 15 insertions(+), 8 deletions(-)

diff --git a/src/forms/rating.rs b/src/forms/rating.rs
index 76efca44..d7a4bd7e 100644
--- a/src/forms/rating.rs
+++ b/src/forms/rating.rs
@@ -12,3 +12,16 @@ pub struct Rating {
     #[validate(maximum = 10)]
     pub rate: i32, //
 }
+
+impl Into<models::Rating> for Rating {
+    fn into(self) -> models::Rating {
+        let mut rating = models::Rating::default(); 
+        rating.obj_id = self.obj_id;
+        rating.category = self.category.into(); //todo change the type of category field to the RateCategory
+        rating.hidden = Some(false); 
+        rating.rate = Some(self.rate);
+        rating.comment = self.comment; 
+
+        rating
+    }
+}
diff --git a/src/models/rating.rs b/src/models/rating.rs
index c9ba7054..7279fc27 100644
--- a/src/models/rating.rs
+++ b/src/models/rating.rs
@@ -28,6 +28,7 @@ pub struct Rating {
     pub created_at: DateTime<Utc>,
     pub updated_at: DateTime<Utc>,
 }
+//todo consider to change the type of category into RateCategory
 
 
 
diff --git a/src/routes/rating/add.rs b/src/routes/rating/add.rs
index 54a1ada5..88ec9b95 100644
--- a/src/routes/rating/add.rs
+++ b/src/routes/rating/add.rs
@@ -31,18 +31,11 @@ pub async fn add_handler(
         Err(_e) => {}
     }
 
-    let mut rating = models::Rating::default(); //todo into trait
+    let mut rating: models::Rating = form.into_inner().into();
     rating.user_id = user.id.clone();
-    rating.obj_id = form.obj_id;
-    rating.category = form.category.into(); //todo change the type of category field to the
-                                            //RateCategory
-    rating.comment = form.comment.clone(); //todo how to do it correctly?
-    rating.hidden = Some(false); //todo add to form
-    rating.rate = Some(form.rate);
 
     db::rating::insert(pg_pool.get_ref(), rating)
         .await
         .map(|rating| JsonResponse::build().set_item(rating).ok("success"))
         .map_err(|err| JsonResponse::<models::Rating>::build().internal_server_error("Failed to insert"))
-    //todo. verify that created_at and updated_at are also added to the response
 }

From 6ae6a484e26b915cf3d0903d67fd0db418b8846e Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Fri, 15 Dec 2023 14:59:14 +0200
Subject: [PATCH 070/284] default_network fix

---
 .dockerignore                   |   0
 .env                            |   2 +
 Cargo.lock                      |   1 +
 Cargo.toml                      |   1 +
 Dockerfile                      |  57 +++++++++---------
 docker-compose.yml              |  49 +++++++---------
 docker/dev/configuration.yaml   |   6 ++
 docker/dev/docker-compose.yml   |  14 ++++-
 docker/local/configuration.yaml |   6 ++
 src/forms/stack.rs              |  19 +-----
 src/helpers/compressor.rs       |  20 +++++++
 src/helpers/mod.rs              |   3 +-
 src/helpers/stack/builder.rs    | 100 +++++++++++++++++---------------
 src/middleware/trydirect.rs     |   2 +-
 src/routes/rating/get.rs        |   1 -
 src/routes/stack/deploy.rs      |   5 +-
 16 files changed, 158 insertions(+), 128 deletions(-)
 create mode 100644 .dockerignore
 create mode 100644 src/helpers/compressor.rs

diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 00000000..e69de29b
diff --git a/.env b/.env
index dffc6728..14bdc640 100644
--- a/.env
+++ b/.env
@@ -1,3 +1,5 @@
+#BUILDKIT_PROGRESS=plain
+#DOCKER_BUILDKIT=1
 DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/stacker
 POSTGRES_USER=postgres
 POSTGRES_PASSWORD=postgres
diff --git a/Cargo.lock b/Cargo.lock
index 6c5834b6..4c8f8db4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3090,6 +3090,7 @@ dependencies = [
  "actix-http",
  "actix-web",
  "actix-web-httpauth",
+ "brotli",
  "chrono",
  "clap",
  "config",
diff --git a/Cargo.toml b/Cargo.toml
index b23bf3b2..14d368b2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -52,6 +52,7 @@ serde_yaml = "0.9"
 lapin = { version = "2.3.1", features = ["serde_json"] }
 futures-lite = "1.13.0"
 clap = { version = "4.4.8", features = ["derive"] }
+brotli = "3.4.0"
 
 [dependencies.sqlx]
 version = "0.6.3"
diff --git a/Dockerfile b/Dockerfile
index 666567ef..29b6b730 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,55 +1,56 @@
 FROM rust:bookworm as builder
 
-RUN apt-get update; \
-    #apt-get install --no-install-recommends -y libpq-dev libssl-dev pkg-config; \
-    apt-get install --no-install-recommends -y libssl-dev; \
-    rm -rf /var/lib/apt/lists/*; \
-    USER=root cargo new --bin app;
+#RUN apt-get update; \
+#    apt-get install --no-install-recommends -y libssl-dev; \
+#    rm -rf /var/lib/apt/lists/*; \
+#    USER=root cargo new --bin app;
 
 RUN cargo install sqlx-cli
 
 WORKDIR /app
 # copy manifests
-COPY ../Cargo.toml .
-COPY ../Cargo.lock .
-COPY ../rustfmt.toml .
-COPY ../Makefile .
-COPY ../docker/local/.env .
-COPY ../docker/local/configuration.yaml .
+COPY ./Cargo.toml .
+COPY ./Cargo.lock .
+COPY ./rustfmt.toml .
+COPY ./Makefile .
+COPY ./docker/local/.env .
+COPY ./docker/local/configuration.yaml .
 
 # build this project to cache dependencies
 #RUN sqlx database create && sqlx migrate run
 
-RUN cargo build --release; \
-    rm src/*.rs
+# build skeleton and remove src after
+#RUN cargo build --release; \
+#    rm src/*.rs
 
-# add .env and secret.key for Docker env
-#RUN touch .env;
-# copy project source and necessary files
-COPY ../src ./src
 
+COPY ./src ./src
+
+# for ls output use BUILDKIT_PROGRESS=plain docker build .
+#RUN ls -la /app/ >&2
 #RUN sqlx migrate run
 #RUN cargo sqlx prepare -- --bin stacker
 
 # rebuild app with project source
-RUN rm -rf ./target/release/deps/stacker*; \
+#RUN rm -rf ./target/release/deps/server*; \
+#    apt-get install --no-install-recommends -y libssl-dev; \
+#    cargo build --release
+
+RUN apt-get update && apt-get install --no-install-recommends -y libssl-dev; \
     cargo build --release
 
-# deploy stage
-FROM debian:bookworm as production
+#RUN ls -la /app/target/release/ >&2
 
+# deploy production
+FROM debian:bookworm-slim as production
+
+RUN apt-get update && apt-get install --no-install-recommends -y libssl-dev;
 # create app directory
 WORKDIR /app
 RUN mkdir ./files && chmod 0777 ./files
 
-# install libpq
-RUN apt-get update; \
-    apt-get install --no-install-recommends -y  libssl-dev  \
-    && rm -rf /var/lib/apt/lists/*
-
 # copy binary and configuration files
-#COPY --from=builder ~/.cargo/bin/sqlx-cli sqlx-cli
-COPY --from=builder /app/target/release/stacker .
+COPY --from=builder /app/target/release/server .
 COPY --from=builder /app/.env .
 COPY --from=builder /app/configuration.yaml .
 COPY --from=builder /usr/local/cargo/bin/sqlx sqlx
@@ -57,4 +58,4 @@ COPY --from=builder /usr/local/cargo/bin/sqlx sqlx
 EXPOSE 8000
 
 # run the binary
-ENTRYPOINT ["/app/stacker"]
+ENTRYPOINT ["/app/server"]
diff --git a/docker-compose.yml b/docker-compose.yml
index 26311c4c..9cba7b20 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -4,16 +4,11 @@ volumes:
   stackerdb:
     driver: local
 
-networks:
-  backend:
-    driver: bridge
-    name: backend
-    external: true
 
 services:
 
   stacker:
-    image: trydirect/stacker:0.0.4
+    image: trydirect/stacker:0.0.5
     build: .
     container_name: stacker
     restart: always
@@ -29,28 +24,24 @@ services:
     environment:
       - RUST_LOG=debug
       - RUST_BACKTRACE=1
-    depends_on:
-      stackerdb:
-        condition: service_healthy
-    networks:
-      - backend
+#    depends_on:
+#      stackerdb:
+#        condition: service_healthy
 
 
-  stackerdb:
-    container_name: stackerdb
-    healthcheck:
-      test: ["CMD-SHELL", "pg_isready -U postgres"]
-      interval: 10s
-      timeout: 5s
-      retries: 5
-    image: postgres:16.0
-    restart: always
-    ports:
-      - 5432:5432
-    env_file:
-      - ./docker/local/.env
-    volumes:
-      - stackerdb:/var/lib/postgresql/data
-      - ./docker/local/postgresql.conf:/etc/postgresql/postgresql.conf
-    networks:
-      - backend
\ No newline at end of file
+#  stackerdb:
+#    container_name: stackerdb
+#    healthcheck:
+#      test: ["CMD-SHELL", "pg_isready -U postgres"]
+#      interval: 10s
+#      timeout: 5s
+#      retries: 5
+#    image: postgres:16.0
+#    restart: always
+#    ports:
+#      - 5432:5432
+#    env_file:
+#      - ./docker/local/.env
+#    volumes:
+#      - stackerdb:/var/lib/postgresql/data
+#      - ./docker/local/postgresql.conf:/etc/postgresql/postgresql.conf
\ No newline at end of file
diff --git a/docker/dev/configuration.yaml b/docker/dev/configuration.yaml
index 5eef969c..5538317c 100644
--- a/docker/dev/configuration.yaml
+++ b/docker/dev/configuration.yaml
@@ -7,3 +7,9 @@ database:
   username: postgres
   password: postgres
   database_name: stacker
+
+amqp:
+  host: 127.0.0.1
+  port: 5672
+  username: guest
+  password: guest
diff --git a/docker/dev/docker-compose.yml b/docker/dev/docker-compose.yml
index bdfdd892..c1a022b2 100644
--- a/docker/dev/docker-compose.yml
+++ b/docker/dev/docker-compose.yml
@@ -4,10 +4,16 @@ volumes:
   stackerdb:
     driver: local
 
+networks:
+  backend:
+    driver: bridge
+    name: backend
+    external: true
+
 services:
 
   stacker:
-    image: trydirect/stacker:0.0.4
+    image: trydirect/stacker:0.0.5
     build: .
     container_name: stacker
     restart: always
@@ -26,6 +32,8 @@ services:
     depends_on:
       stackerdb:
         condition: service_healthy
+    networks:
+      - backend
 
 
   stackerdb:
@@ -43,4 +51,6 @@ services:
       - ./.env
     volumes:
       - stackerdb:/var/lib/postgresql/data
-      - ./postgresql.conf:/etc/postgresql/postgresql.conf
\ No newline at end of file
+      - ./postgresql.conf:/etc/postgresql/postgresql.conf
+    networks:
+      - backend
diff --git a/docker/local/configuration.yaml b/docker/local/configuration.yaml
index 9c1848f5..a0bb4c00 100644
--- a/docker/local/configuration.yaml
+++ b/docker/local/configuration.yaml
@@ -7,3 +7,9 @@ database:
   username: postgres
   password: postgres
   database_name: stacker
+
+amqp:
+  host: 127.0.0.1
+  port: 5672
+  username: guest
+  password: guest
diff --git a/src/forms/stack.rs b/src/forms/stack.rs
index b67a8566..cd8f7868 100644
--- a/src/forms/stack.rs
+++ b/src/forms/stack.rs
@@ -34,14 +34,6 @@ pub struct Port {
     pub container_port: Option<String>
 }
 
-// #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-// pub struct Ports {
-//     #[serde(rename(deserialize = "sharedPorts"))]
-//     #[serde(rename(serialize = "shared_ports"))]
-//     // #[serde(alias = "shared_ports")]
-//     pub shared_ports: Option<Vec<Port>>,
-//     pub ports: Option<Vec<String>>,
-// }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct DockerImage {
@@ -155,6 +147,7 @@ pub struct StackPayload {
     #[serde(rename = "selected_plan")]
     pub selected_plan: String,
     pub custom: Custom,
+    pub docker_compose: Option<Vec<u8>>
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
@@ -261,8 +254,6 @@ pub struct App {
     pub environment: Environment,
     #[serde(flatten)]
     pub network: ServiceNetworks,
-    // #[serde(flatten)]
-    // pub ports: Ports,
     #[serde(rename(deserialize = "sharedPorts"))]
     #[serde(rename(serialize = "shared_ports"))]
     #[serde(alias = "shared_ports")]
@@ -305,20 +296,12 @@ pub struct Web {
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct Feature {
-    // #[serde(rename(deserialize = "sharedPorts"))]
-    // #[serde(rename(serialize = "shared_ports"))]
-    // #[serde(alias = "shared_ports")]
-    // pub shared_ports: Option<Vec<Port>>,
     #[serde(flatten)]
     pub app: App,
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Service {
-    // #[serde(rename(deserialize = "sharedPorts"))]
-    // #[serde(rename(serialize = "shared_ports"))]
-    // #[serde(alias = "shared_ports")]
-    // pub shared_ports: Option<Vec<Port>>,
     #[serde(flatten)]
     pub(crate) app: App,
 }
diff --git a/src/helpers/compressor.rs b/src/helpers/compressor.rs
new file mode 100644
index 00000000..8061329c
--- /dev/null
+++ b/src/helpers/compressor.rs
@@ -0,0 +1,20 @@
+use brotli::{CompressorWriter, Decompressor};
+use std::io::{Read, Write};
+
+pub fn compress(input: &str) -> Vec<u8> {
+    let mut compressed = Vec::new();
+    let mut compressor = CompressorWriter::new(
+        &mut compressed, 4096, 11, 22
+    );
+    compressor.write_all(input.as_bytes()).unwrap();
+    compressor.flush().unwrap();
+    drop(compressor);
+    compressed
+}
+
+pub fn decompress(input: &[u8]) -> String {
+    let mut decompressed = String::new();
+    let mut decompressor = Decompressor::new(input, 4096);
+    decompressor.read_to_string(&mut decompressed).unwrap();
+    decompressed
+}
diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs
index e5439f74..53978753 100644
--- a/src/helpers/mod.rs
+++ b/src/helpers/mod.rs
@@ -1,5 +1,6 @@
 pub mod client;
 pub(crate) mod json;
 pub(crate) mod stack;
-
 pub use json::*;
+pub(crate) mod compressor;
+pub use compressor::*;
\ No newline at end of file
diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index 1db77c54..3df222b3 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -78,33 +78,23 @@ impl TryInto<Port> for stack::Port {
 impl TryInto<Networks> for stack::ServiceNetworks {
     type Error = ();
     fn try_into(self) -> Result<Networks, Self::Error> {
-        let mut networks = vec!["default_network".to_string()];
-        if self.network.is_some() {
-            networks.append(&mut self.network.unwrap());
-        }
-        Ok(Networks::Simple(networks))
-    }
-}
-
-fn convert_shared_ports(ports: Option<Vec<stack::Port>>) -> Result<Vec<Port>, String> {
-    tracing::debug!("convert shared ports {:?}", &ports);
-    let mut _ports: Vec<Port> = vec![];
-    match ports {
-        Some(ports) => {
-            tracing::debug!("Ports >>>> {:?}", ports);
-            for port in ports {
+        let mut default_networks = vec!["default_network".to_string()];
+        let nets = match self.network {
+            Some(mut _nets) => {
+                if !_nets.contains(&"default_network".to_string()) {
+                    _nets.append(&mut default_networks);
+                }
+                _nets
             }
-        }
-        None => {
-            tracing::debug!("No ports defined by user");
-            return Ok(_ports);
-        }
+            None => {
+               default_networks
+            }
+        };
+        Ok(Networks::Simple(nets))
     }
-
-    tracing::debug!("ports {:?}", _ports);
-    Ok(_ports)
 }
 
+
 fn is_named_docker_volume(volume: &str) -> bool {
     // Docker named volumes typically don't contain special characters or slashes
     // They are alphanumeric and may include underscores or hyphens
@@ -170,14 +160,23 @@ impl TryIntoService for App {
 impl Into<IndexMap<String, MapOrEmpty<NetworkSettings>>> for stack::ComposeNetworks {
     fn into(self) -> IndexMap<String, MapOrEmpty<NetworkSettings>> {
 
-        // tracing::debug!("networks found {:?}", self.networks);
-        let mut networks = vec!["default_network".to_string()];
-        if self.networks.is_some() {
-            networks.append(&mut self.networks.unwrap());
-        }
+        let mut default_network = vec!["default_network".to_string()];
+
+        let networks = match self.networks {
+            None => {
+                default_network
+            }
+            Some(mut nets) => {
+                if !nets.contains(&"default_network".to_string()) {
+                    nets.append(&mut default_network);
+                }
+                nets
+            }
+        };
+
         let networks = networks
             .into_iter()
-            .map(|net|
+            .map(|net| {
                 (net,
                  MapOrEmpty::Map(
                      NetworkSettings {
@@ -193,8 +192,12 @@ impl Into<IndexMap<String, MapOrEmpty<NetworkSettings>>> for stack::ComposeNetwo
                          name: Some("default".to_string()),
                      }
                  ))
+            }
             )
             .collect::<IndexMap<String, _>>();
+
+        tracing::debug!("networks collected {:?}", &networks);
+
         networks
     }
 }
@@ -203,26 +206,29 @@ impl Into<IndexMap<String, MapOrEmpty<NetworkSettings>>> for stack::ComposeNetwo
 pub fn extract_named_volumes(app: App) -> IndexMap<String, MapOrEmpty<ComposeVolume>> {
 
     let mut named_volumes = IndexMap::default();
+    if app.volumes.is_none() {
+        return named_volumes;
+    }
 
-        let volumes = app.volumes
-            .unwrap()
-            .into_iter()
-            .filter(|volume| is_named_docker_volume(
-                volume.host_path.clone().unwrap().as_str())
-            )
-            .map(|volume| {
-                let k = volume.host_path.clone().unwrap();
-                (k.clone(), MapOrEmpty::Map(ComposeVolume {
-                    driver: None,
-                    driver_opts: Default::default(),
-                    external: None,
-                    labels: Default::default(),
-                    name: Some(k.clone())
-                }))
-            })
-            .collect::<IndexMap<String, MapOrEmpty<ComposeVolume>>>();
+    let volumes = app.volumes
+        .unwrap()
+        .into_iter()
+        .filter(|volume| is_named_docker_volume(
+            volume.host_path.clone().unwrap().as_str())
+        )
+        .map(|volume| {
+            let k = volume.host_path.clone().unwrap();
+            (k.clone(), MapOrEmpty::Map(ComposeVolume {
+                driver: None,
+                driver_opts: Default::default(),
+                external: None,
+                labels: Default::default(),
+                name: Some(k.clone())
+            }))
+        })
+        .collect::<IndexMap<String, MapOrEmpty<ComposeVolume>>>();
 
-        named_volumes.extend(volumes);
+    named_volumes.extend(volumes);
     // tracing::debug!("Named volumes: {:?}", named_volumes);
 
     named_volumes
diff --git a/src/middleware/trydirect.rs b/src/middleware/trydirect.rs
index bbe5e4bf..c8bbfd13 100644
--- a/src/middleware/trydirect.rs
+++ b/src/middleware/trydirect.rs
@@ -5,7 +5,7 @@ use futures::future::{FutureExt};
 use reqwest::header::{ACCEPT, CONTENT_TYPE};
 use std::sync::Arc;
 
-#[tracing::instrument(name = "Trydirect bearer guard.")]
+#[tracing::instrument(name = "TryDirect bearer guard.")]
 pub async fn bearer_guard( req: ServiceRequest, credentials: BearerAuth) -> Result<ServiceRequest, (Error, ServiceRequest)> {
     let settings = req.app_data::<web::Data<Settings>>().unwrap();
     let token = credentials.token();
diff --git a/src/routes/rating/get.rs b/src/routes/rating/get.rs
index e781d9a8..0db78015 100644
--- a/src/routes/rating/get.rs
+++ b/src/routes/rating/get.rs
@@ -46,7 +46,6 @@ pub async fn get_handler(
 pub async fn list_handler(path: web::Path<()>, pool: web::Data<PgPool>) -> Result<impl Responder> {
     /// Get ratings of all users
     let query_span = tracing::info_span!("Get all rates.");
-    // let category = path.0;
     match sqlx::query_as!(models::Rating, r"SELECT * FROM rating")
         .fetch_all(pool.get_ref())
         .instrument(query_span)
diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs
index d1224c59..625fba69 100644
--- a/src/routes/stack/deploy.rs
+++ b/src/routes/stack/deploy.rs
@@ -11,6 +11,7 @@ use lapin::{
 };
 use sqlx::PgPool;
 use std::sync::Arc;
+use crate::helpers::compressor::compress;
 
 
 #[tracing::instrument(name = "Deploy for every user. Admin endpoint")]
@@ -52,7 +53,7 @@ pub async fn add(
         Some(stack) => {
             let id = stack.id.clone();
             let dc = DcBuilder::new(stack);
-            dc.build();
+            let fc = dc.build();
 
             let addr = sets.amqp.connection_string();
             let routing_key = "install.start.tfa.all.all".to_string();
@@ -72,6 +73,8 @@ pub async fn add(
             stack_data.user_token = Some(user.id.clone());
             stack_data.user_email = Some(user.email.clone());
             stack_data.stack_code = stack_data.custom.custom_stack_code.clone();
+            let compressed = fc.unwrap_or("".to_string());
+            stack_data.docker_compose = Some(compress(compressed.as_str()));
 
             let payload = serde_json::to_string::<StackPayload>(&stack_data).unwrap();
             let _payload = payload.as_bytes();

From 3453c3e6900afffad4c9eff1ec9655b7e553e30a Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 16 Dec 2023 09:32:49 +0200
Subject: [PATCH 071/284] issue-auth models into files

---
 src/models/mod.rs          |  6 ++++++
 src/models/product.rs      | 16 ++++++++++++++
 src/models/ratecategory.rs | 22 +++++++++++++++++++
 src/models/rating.rs       | 44 --------------------------------------
 src/models/rules.rs        |  5 +++++
 5 files changed, 49 insertions(+), 44 deletions(-)
 create mode 100644 src/models/product.rs
 create mode 100644 src/models/ratecategory.rs
 create mode 100644 src/models/rules.rs

diff --git a/src/models/mod.rs b/src/models/mod.rs
index b6951144..a17ea327 100644
--- a/src/models/mod.rs
+++ b/src/models/mod.rs
@@ -1,4 +1,7 @@
 mod client;
+mod product;
+mod ratecategory;
+mod rules;
 pub mod rating;
 pub mod stack;
 pub mod user;
@@ -7,3 +10,6 @@ pub use client::*;
 pub use rating::*;
 pub use stack::*;
 pub use user::*;
+pub use product::*;
+pub use ratecategory::*;
+pub use rules::*;
diff --git a/src/models/product.rs b/src/models/product.rs
new file mode 100644
index 00000000..992818db
--- /dev/null
+++ b/src/models/product.rs
@@ -0,0 +1,16 @@
+use chrono::{DateTime, Utc};
+
+pub struct Product {
+    // Product - is an external object that we want to store in the database,
+    // that can be a stack or an app in the stack. feature, service, web app etc.
+    // id - is a unique identifier for the product
+    // user_id - is a unique identifier for the user
+    // rating - is a rating of the product
+    // product type stack & app,
+    // id is generated based on the product type and external obj_id
+    pub id: i32,          //primary key, for better data management
+    pub obj_id: i32,      // external product ID db, no autoincrement, example: 100
+    pub obj_type: String, // stack | app, unique index
+    pub created_at: DateTime<Utc>,
+    pub updated_at: DateTime<Utc>,
+}
diff --git a/src/models/ratecategory.rs b/src/models/ratecategory.rs
new file mode 100644
index 00000000..82eefd8c
--- /dev/null
+++ b/src/models/ratecategory.rs
@@ -0,0 +1,22 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(sqlx::Type, Serialize, Deserialize, Debug, Clone, Copy)]
+#[sqlx(rename_all = "lowercase", type_name = "varchar")]
+pub enum RateCategory {
+    Application, // app, feature, extension
+    Cloud,       // is user satisfied working with this cloud
+    Stack,       // app stack
+    DeploymentSpeed,
+    Documentation,
+    Design,
+    TechSupport,
+    Price,
+    MemoryUsage,
+}
+
+impl Into<String> for RateCategory {
+    fn into(self) -> String {
+        format!("{:?}", self)
+    }
+}
+
diff --git a/src/models/rating.rs b/src/models/rating.rs
index 7279fc27..beb5ef1a 100644
--- a/src/models/rating.rs
+++ b/src/models/rating.rs
@@ -1,21 +1,6 @@
 use chrono::{DateTime, Utc};
 use serde::{Deserialize, Serialize};
 
-pub struct Product {
-    // Product - is an external object that we want to store in the database,
-    // that can be a stack or an app in the stack. feature, service, web app etc.
-    // id - is a unique identifier for the product
-    // user_id - is a unique identifier for the user
-    // rating - is a rating of the product
-    // product type stack & app,
-    // id is generated based on the product type and external obj_id
-    pub id: i32,          //primary key, for better data management
-    pub obj_id: i32,      // external product ID db, no autoincrement, example: 100
-    pub obj_type: String, // stack | app, unique index
-    pub created_at: DateTime<Utc>,
-    pub updated_at: DateTime<Utc>,
-}
-
 #[derive(Debug, Serialize, Default)]
 pub struct Rating {
     pub id: i32,
@@ -28,32 +13,3 @@ pub struct Rating {
     pub created_at: DateTime<Utc>,
     pub updated_at: DateTime<Utc>,
 }
-//todo consider to change the type of category into RateCategory
-
-
-
-#[derive(sqlx::Type, Serialize, Deserialize, Debug, Clone, Copy)]
-#[sqlx(rename_all = "lowercase", type_name = "varchar")]
-pub enum RateCategory {
-    Application, // app, feature, extension
-    Cloud,       // is user satisfied working with this cloud
-    Stack,       // app stack
-    DeploymentSpeed,
-    Documentation,
-    Design,
-    TechSupport,
-    Price,
-    MemoryUsage,
-}
-
-impl Into<String> for RateCategory {
-    fn into(self) -> String {
-        format!("{:?}", self)
-    }
-}
-
-pub struct Rules {
-    //-> Product.id
-    // example: allow to add only a single comment
-    comments_per_user: i32, // default = 1
-}
diff --git a/src/models/rules.rs b/src/models/rules.rs
new file mode 100644
index 00000000..58afbd97
--- /dev/null
+++ b/src/models/rules.rs
@@ -0,0 +1,5 @@
+pub struct Rules {
+    //-> Product.id
+    // example: allow to add only a single comment
+    comments_per_user: i32, // default = 1
+}

From dd202db25de3a40bf8fdb02b34f47184fafb2d0f Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 16 Dec 2023 10:18:30 +0200
Subject: [PATCH 072/284] issue-auth postgres enum rate_category

---
 .../20230903063840_creating_rating_tables.down.sql |  2 ++
 .../20230903063840_creating_rating_tables.up.sql   | 14 +++++++++++++-
 2 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/migrations/20230903063840_creating_rating_tables.down.sql b/migrations/20230903063840_creating_rating_tables.down.sql
index e12e4ab3..b32b52b5 100644
--- a/migrations/20230903063840_creating_rating_tables.down.sql
+++ b/migrations/20230903063840_creating_rating_tables.down.sql
@@ -6,3 +6,5 @@ DROP INDEX idx_obj_id_rating_id;
 
 DROP table rating;
 DROP table product;
+
+DROP TYPE rate_category;
diff --git a/migrations/20230903063840_creating_rating_tables.up.sql b/migrations/20230903063840_creating_rating_tables.up.sql
index 579bef6f..945cdb61 100644
--- a/migrations/20230903063840_creating_rating_tables.up.sql
+++ b/migrations/20230903063840_creating_rating_tables.up.sql
@@ -1,5 +1,17 @@
 -- Add up migration script here
 
+CREATE TYPE rate_category AS ENUM (
+    'Application',
+    'Cloud',      
+    'Stack',
+    'DeploymentSpeed',
+    'Documentation',
+    'Design',
+    'TechSupport',
+    'Price',
+    'MemoryUsage'
+);
+
 CREATE TABLE product (
     id integer NOT NULL, PRIMARY KEY(id),
     obj_id integer NOT NULL,
@@ -12,7 +24,7 @@ CREATE TABLE rating (
     id serial,
     user_id VARCHAR(50) NOT NULL,
     obj_id integer NOT NULL,
-    category VARCHAR(255) NOT NULL,
+    category rate_category NOT NULL,
     comment TEXT DEFAULT NULL,
     hidden BOOLEAN DEFAULT FALSE,
     rate INTEGER,

From 412d0da6a6c2bd9c67a8edc1c3950a9c43b90a4a Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 16 Dec 2023 17:42:45 +0200
Subject: [PATCH 073/284] issue-auth RateCategory enum saved in db

---
 ...230903063840_creating_rating_tables.up.sql | 18 ++--
 src/db/client.rs                              |  8 +-
 src/db/product.rs                             |  7 +-
 src/db/rating.rs                              | 83 +++++++++++++++++--
 src/models/ratecategory.rs                    |  7 +-
 src/models/rating.rs                          |  3 +-
 src/routes/rating/get.rs                      | 50 ++---------
 7 files changed, 114 insertions(+), 62 deletions(-)

diff --git a/migrations/20230903063840_creating_rating_tables.up.sql b/migrations/20230903063840_creating_rating_tables.up.sql
index 945cdb61..28422706 100644
--- a/migrations/20230903063840_creating_rating_tables.up.sql
+++ b/migrations/20230903063840_creating_rating_tables.up.sql
@@ -1,15 +1,15 @@
 -- Add up migration script here
 
 CREATE TYPE rate_category AS ENUM (
-    'Application',
-    'Cloud',      
-    'Stack',
-    'DeploymentSpeed',
-    'Documentation',
-    'Design',
-    'TechSupport',
-    'Price',
-    'MemoryUsage'
+    'application',
+    'cloud',      
+    'stack',
+    'deploymentSpeed',
+    'documentation',
+    'design',
+    'techSupport',
+    'price',
+    'memoryUsage'
 );
 
 CREATE TABLE product (
diff --git a/src/db/client.rs b/src/db/client.rs
index 750eefd2..477dbf5a 100644
--- a/src/db/client.rs
+++ b/src/db/client.rs
@@ -6,7 +6,8 @@ pub async fn update(pool: &PgPool, client: models::Client) -> Result<models::Cli
     let query_span = tracing::info_span!("Updating client into the database");
     sqlx::query!(
         r#"
-        UPDATE client SET 
+        UPDATE client
+        SET 
             secret=$1,
             updated_at=NOW() at time zone 'utc'
         WHERE id = $2
@@ -33,9 +34,12 @@ pub async fn fetch(pool: &PgPool, id: i32) -> Result<models::Client, String> {
         models::Client,
         r#"
         SELECT
-           id, user_id, secret 
+           id,
+           user_id,
+           secret 
         FROM client c
         WHERE c.id = $1
+        LIMIT 1
         "#,
         id,
     )
diff --git a/src/db/product.rs b/src/db/product.rs
index d409d57b..56431b1c 100644
--- a/src/db/product.rs
+++ b/src/db/product.rs
@@ -6,7 +6,12 @@ pub async fn fetch_by_obj(pg_pool: &PgPool, obj_id: i32) -> Result<models::Produ
     let query_span = tracing::info_span!("Check product existence by id.");
     sqlx::query_as!(
         models::Product,
-        r"SELECT * FROM product WHERE obj_id = $1",
+        r#"SELECT
+            *
+         FROM product
+         WHERE obj_id = $1
+         LIMIT 1
+         "#,
         obj_id
     )
     .fetch_one(pg_pool)
diff --git a/src/db/rating.rs b/src/db/rating.rs
index 7d6a12eb..e5cb40f7 100644
--- a/src/db/rating.rs
+++ b/src/db/rating.rs
@@ -2,22 +2,93 @@ use sqlx::PgPool;
 use crate::models;
 use tracing::Instrument;
 
+pub async fn fetch_all(pool: &PgPool) -> Result<Vec<models::Rating>, String> {
+    let query_span = tracing::info_span!("Fetch all ratings.");
+    sqlx::query_as!(
+        models::Rating,
+        r#"SELECT 
+            id,
+            user_id,
+            obj_id,
+            category as "category: _",
+            comment,
+            hidden,
+            rate,
+            created_at,
+            updated_at
+        FROM rating"#
+    )
+    .fetch_all(pool)
+    .instrument(query_span)
+    .await
+    .map_err(|e| {
+        tracing::error!("Failed to execute fetch query: {:?}", e);
+        "".to_string()
+    })
+}
+
+pub async fn fetch(pool: &PgPool, id: i32) -> Result<models::Rating, String> {
+    let query_span = tracing::info_span!("Fetch rating by id");
+    sqlx::query_as!(
+        models::Rating,
+        r#"SELECT 
+            id,
+            user_id,
+            obj_id,
+            category as "category: _",
+            comment,
+            hidden,
+            rate,
+            created_at,
+            updated_at
+        FROM rating
+        WHERE id=$1
+        LIMIT 1"#,
+        id
+    )
+    .fetch_one(pool)
+    .instrument(query_span)
+    .await
+    .map_err(|e| {
+        match e {
+            sqlx::Error::RowNotFound => "rating not found".to_string(),
+            s => {
+                tracing::error!("Failed to execute fetch query: {:?}", s);
+                "".to_string()
+            }
+        }
+    })
+}
+
 pub async fn fetch_by_obj_and_user_and_category(pool: &PgPool, obj_id: i32, user_id: String, category: models::RateCategory) -> Result<models::Rating, String> {
-    let query_span = tracing::info_span!("Search for existing vote.");
+    let query_span = tracing::info_span!("Fetch rating by obj, user and category.");
     sqlx::query_as!(
         models::Rating,
-        r"SELECT * FROM rating where user_id=$1 AND obj_id=$2 AND category=$3 LIMIT 1",
+        r#"SELECT 
+            id,
+            user_id,
+            obj_id,
+            category as "category: _",
+            comment,
+            hidden,
+            rate,
+            created_at,
+            updated_at
+        FROM rating
+        WHERE user_id=$1
+            AND obj_id=$2
+            AND category=$3
+        LIMIT 1"#,
         user_id,
         obj_id,
-        "ok" //todo put there the category
-        //category.into() //todo
+        category as _ 
     )
     .fetch_one(pool)
     .instrument(query_span)
     .await
     .map_err(|e| {
         match e {
-            sqlx::Error::RowNotFound => "client not found".to_string(),
+            sqlx::Error::RowNotFound => "fetch not found".to_string(),
             s => {
                 tracing::error!("Failed to execute fetch query: {:?}", s);
                 "".to_string()
@@ -36,7 +107,7 @@ pub async fn insert(pool: &PgPool, mut rating: models::Rating) -> Result<models:
         "#,
         rating.user_id,
         rating.obj_id,
-        rating.category,
+        rating.category as _,
         rating.comment,
         rating.hidden,
         rating.rate
diff --git a/src/models/ratecategory.rs b/src/models/ratecategory.rs
index 82eefd8c..8228ae0e 100644
--- a/src/models/ratecategory.rs
+++ b/src/models/ratecategory.rs
@@ -1,7 +1,7 @@
 use serde::{Deserialize, Serialize};
 
 #[derive(sqlx::Type, Serialize, Deserialize, Debug, Clone, Copy)]
-#[sqlx(rename_all = "lowercase", type_name = "varchar")]
+#[sqlx(rename_all = "lowercase", type_name = "rate_category")]
 pub enum RateCategory {
     Application, // app, feature, extension
     Cloud,       // is user satisfied working with this cloud
@@ -20,3 +20,8 @@ impl Into<String> for RateCategory {
     }
 }
 
+impl Default for RateCategory {
+    fn default() -> Self {
+        RateCategory::Application
+    }
+}
diff --git a/src/models/rating.rs b/src/models/rating.rs
index beb5ef1a..e1522dea 100644
--- a/src/models/rating.rs
+++ b/src/models/rating.rs
@@ -1,12 +1,13 @@
 use chrono::{DateTime, Utc};
 use serde::{Deserialize, Serialize};
+use crate::models;
 
 #[derive(Debug, Serialize, Default)]
 pub struct Rating {
     pub id: i32,
     pub user_id: String, // external user_id, 100, taken using token (middleware?)
     pub obj_id: i32,     // id of the external object
-    pub category: String, // rating of product | rating of service etc
+    pub category: models::RateCategory, // rating of product | rating of service etc
     pub comment: Option<String>, // always linked to a product
     pub hidden: Option<bool>, // rating can be hidden for non-adequate user behaviour
     pub rate: Option<i32>,
diff --git a/src/routes/rating/get.rs b/src/routes/rating/get.rs
index 49fccb47..86c0e570 100644
--- a/src/routes/rating/get.rs
+++ b/src/routes/rating/get.rs
@@ -1,5 +1,6 @@
 use crate::helpers::JsonResponse;
 use crate::models;
+use crate::db;
 use actix_web::{get, web, Responder, Result};
 use sqlx::PgPool;
 use tracing::Instrument;
@@ -15,53 +16,18 @@ pub async fn get_handler(
     path: web::Path<(i32,)>,
     pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
-    /// Get rating of any user
     let rate_id = path.0;
-    let query_span = tracing::info_span!("Search for rate id={}.", rate_id);
-    match sqlx::query_as!(
-        models::Rating,
-        r"SELECT * FROM rating WHERE id=$1 LIMIT 1",
-        rate_id
-    )
-    .fetch_one(pool.get_ref())
-    .instrument(query_span)
-    .await
-    {
-        Ok(rating) => {
-            tracing::info!("rating found: {:?}", rating.id);
-            return Ok(JsonResponse::build().set_item(Some(rating)).ok("OK"));
-        }
-        Err(sqlx::Error::RowNotFound) => {
-            return Err(JsonResponse::<models::Rating>::build().not_found(""));
-        }
-        Err(e) => {
-            tracing::error!("Failed to fetch rating, error: {:?}", e);
-            return Err(JsonResponse::<models::Rating>::build().internal_server_error(""));
-        }
-    }
+    db::rating::fetch(pool.get_ref(), rate_id)
+        .await
+        .map(|rating| JsonResponse::build().set_item(rating).ok("OK"))
+        .map_err(|err| JsonResponse::<models::Rating>::build().not_found("not found"))
 }
 
 #[tracing::instrument(name = "Get all ratings.")]
 #[get("")]
 pub async fn list_handler(path: web::Path<()>, pool: web::Data<PgPool>) -> Result<impl Responder> {
-    /// Get ratings of all users
-    let query_span = tracing::info_span!("Get all rates.");
-    // let category = path.0;
-    match sqlx::query_as!(models::Rating, r"SELECT * FROM rating")
-        .fetch_all(pool.get_ref())
-        .instrument(query_span)
+    db::rating::fetch_all(pool.get_ref())
         .await
-    {
-        Ok(rating) => {
-            tracing::info!("Ratings found: {:?}", rating.len());
-            return Ok(JsonResponse::build().set_list(rating).ok("OK"));
-        }
-        Err(sqlx::Error::RowNotFound) => {
-            return Err(JsonResponse::<models::Rating>::build().not_found(""));
-        }
-        Err(e) => {
-            tracing::error!("Failed to fetch rating, error: {:?}", e);
-            return Err(JsonResponse::<models::Rating>::build().internal_server_error(""));
-        }
-    }
+        .map(|ratings| JsonResponse::build().set_list(ratings).ok("OK"))
+        .map_err(|err| JsonResponse::<models::Rating>::build().internal_server_error(""))
 }

From ecf28fef289a9d7e7e7224d43e5eda2257d11e09 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sun, 17 Dec 2023 11:03:01 +0200
Subject: [PATCH 074/284] issue-auth stack get refactor

---
 src/db/mod.rs           |  1 +
 src/db/stack.rs         | 30 ++++++++++++++++++++++++++++++
 src/routes/stack/get.rs | 39 ++++++++++++++-------------------------
 3 files changed, 45 insertions(+), 25 deletions(-)
 create mode 100644 src/db/stack.rs

diff --git a/src/db/mod.rs b/src/db/mod.rs
index 0aec0371..a4fe3ae7 100644
--- a/src/db/mod.rs
+++ b/src/db/mod.rs
@@ -1,3 +1,4 @@
 pub mod client;
 pub mod product;
 pub mod rating;
+pub mod stack;
diff --git a/src/db/stack.rs b/src/db/stack.rs
new file mode 100644
index 00000000..8b16ef63
--- /dev/null
+++ b/src/db/stack.rs
@@ -0,0 +1,30 @@
+use sqlx::PgPool;
+use crate::models;
+use tracing::Instrument;
+
+pub async fn fetch(pool: &PgPool, id: i32) -> Result<models::Stack, String> {
+    tracing::info!("Fecth stack {}", id);
+    sqlx::query_as!(
+        models::Stack,
+        r#"
+        SELECT
+            *
+        FROM user_stack
+        WHERE id=$1
+        LIMIT 1
+        "#,
+        id
+    )
+    .fetch_one(pool)
+    .await
+    .map_err(|err| {
+        match err {
+            sqlx::Error::RowNotFound => "".to_string(),
+            e => {
+                tracing::error!("Failed to fetch stack, error: {:?}", e);
+                return "Could not fetch data".to_string();
+            }
+
+        }
+    })
+}
diff --git a/src/routes/stack/get.rs b/src/routes/stack/get.rs
index 88114dc5..2538b693 100644
--- a/src/routes/stack/get.rs
+++ b/src/routes/stack/get.rs
@@ -1,6 +1,6 @@
 use crate::helpers::JsonResponse;
 use crate::models;
-use crate::models::user::User;
+use crate::db;
 use actix_web::{get, web, Responder, Result};
 use sqlx::PgPool;
 use std::convert::From;
@@ -10,41 +10,30 @@ use std::sync::Arc;
 #[tracing::instrument(name = "Get logged user stack.")]
 #[get("/{id}")]
 pub async fn item(
-    user: web::ReqData<Arc<User>>,
+    user: web::ReqData<Arc<models::User>>,
     path: web::Path<(i32,)>,
-    pool: web::Data<PgPool>,
+    pool: web::Data<PgPool>, //&Web::Data<PgPool>
+                             //get_ref
+                             //*
 ) -> Result<impl Responder> {
     /// Get stack apps of logged user only
     let (id,) = path.into_inner();
 
-    tracing::info!("User {:?} gets stack by id {:?}", user.id, id);
-    match sqlx::query_as!(
-        models::Stack,
-        r#"
-        SELECT * FROM user_stack WHERE id=$1 AND user_id=$2 LIMIT 1
-        "#,
-        id,
-        user.id
-    )
-    .fetch_one(pool.get_ref())
-    .await
-    {
-        Ok(stack) => {
-            tracing::info!("Stack found: {:?}", stack.id,);
-            return Ok(JsonResponse::build().set_item(Some(stack)).ok("OK"));
-        }
-        Err(sqlx::Error::RowNotFound) => Err(JsonResponse::<models::Stack>::build().not_found("Record not found")),
-        Err(e) => {
-            tracing::error!("Failed to fetch stack, error: {:?}", e);
-            return Err(JsonResponse::<models::Stack>::build().internal_server_error("Could not fetch data"));
-        }
+    let stack = db::stack::fetch(pool.get_ref(), id) 
+        .await
+        .map_err(|err| JsonResponse::<models::Stack>::build().not_found("Record not found"))?;
+
+    if stack.user_id != user.id {
+        return Err(JsonResponse::<models::Stack>::build().bad_request("Forbidden"));
     }
+
+    Ok(JsonResponse::build().set_item(Some(stack)).ok("OK"))
 }
 
 #[tracing::instrument(name = "Get user's stack list.")]
 #[get("/user/{id}")]
 pub async fn list(
-    user: web::ReqData<Arc<User>>,
+    user: web::ReqData<Arc<models::User>>,
     path: web::Path<(String,)>,
     pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {

From e6600986dd180903bebcb5223099ba0c24b9d5d5 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Mon, 18 Dec 2023 17:01:07 +0200
Subject: [PATCH 075/284] issue-auth new command. updated

---
 src/console/commands/appclient/new.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/console/commands/appclient/new.rs b/src/console/commands/appclient/new.rs
index 448ac788..ada01acb 100644
--- a/src/console/commands/appclient/new.rs
+++ b/src/console/commands/appclient/new.rs
@@ -26,7 +26,7 @@ impl crate::console::commands::CallableTrait for NewCommand {
 
             //todo get user from trydirect
             let user = crate::models::user::User {
-                id: "first_name".to_string(),
+                id: format!("{}", self.user_id),
                 first_name: "first_name".to_string(),
                 last_name: "last_name".to_string(),
                 email: "email".to_string(),

From 9e4042a1d13c34dc2a1084f1b6dc286fc00cb79c Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Mon, 18 Dec 2023 17:14:00 +0200
Subject: [PATCH 076/284] issue-auth routes/stack/get.rs

---
 src/db/stack.rs         | 21 +++++++++++++++++++++
 src/routes/stack/get.rs | 37 ++++++-------------------------------
 2 files changed, 27 insertions(+), 31 deletions(-)

diff --git a/src/db/stack.rs b/src/db/stack.rs
index 8b16ef63..5cb2f92a 100644
--- a/src/db/stack.rs
+++ b/src/db/stack.rs
@@ -28,3 +28,24 @@ pub async fn fetch(pool: &PgPool, id: i32) -> Result<models::Stack, String> {
         }
     })
 }
+
+pub async fn fetch_by_user(pool: &PgPool, user_id: String) -> Result<Vec<models::Stack>, String> {
+    let query_span = tracing::info_span!("Fetch stacks by user id.");
+    sqlx::query_as!(
+        models::Stack,
+        r#"
+        SELECT
+            *
+        FROM user_stack
+        WHERE user_id=$1
+        "#,
+        user_id
+    )
+    .fetch_all(pool)
+    .instrument(query_span)
+    .await
+    .map_err(|err| {
+        tracing::error!("Failed to fetch stack, error: {:?}", err);
+        "".to_string()
+    })
+}
diff --git a/src/routes/stack/get.rs b/src/routes/stack/get.rs
index 2538b693..7ac8fb50 100644
--- a/src/routes/stack/get.rs
+++ b/src/routes/stack/get.rs
@@ -12,9 +12,7 @@ use std::sync::Arc;
 pub async fn item(
     user: web::ReqData<Arc<models::User>>,
     path: web::Path<(i32,)>,
-    pool: web::Data<PgPool>, //&Web::Data<PgPool>
-                             //get_ref
-                             //*
+    pool: web::Data<PgPool>, 
 ) -> Result<impl Responder> {
     /// Get stack apps of logged user only
     let (id,) = path.into_inner();
@@ -40,33 +38,10 @@ pub async fn list(
     /// This is admin endpoint, used by a m2m app, client app is confidential
     /// it should return stacks by user id
     /// in order to pass validation at external deployment service
-    let (id,) = path.into_inner();
-    tracing::info!("Logged user: {:?}", user.id);
-    tracing::info!("Get stack list for user {:?}", id);
-
-    let query_span = tracing::info_span!("Get stacks by user id.");
+    let (user_id,) = path.into_inner();
 
-    match sqlx::query_as!(
-        models::Stack,
-        r#"
-        SELECT * FROM user_stack WHERE user_id=$1
-        "#,
-        id
-    )
-    .fetch_all(pool.get_ref())
-    .instrument(query_span)
-    .await
-    {
-        Ok(list) => {
-            return Ok(JsonResponse::build().set_list(list).ok("OK"));
-        }
-        Err(sqlx::Error::RowNotFound) => {
-            tracing::error!("No stacks found for user: {:?}", &user.id);
-            return Err(JsonResponse::<models::Stack>::build().not_found("No stacks found for user"));
-        }
-        Err(e) => {
-            tracing::error!("Failed to fetch stack, error: {:?}", e);
-            return Err(JsonResponse::<models::Stack>::build().internal_server_error("Could not fetch"));
-        }
-    }
+    db::stack::fetch_by_user(pool.get_ref(), user_id) 
+        .await
+        .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(""))
+        .map(|stacks| JsonResponse::build().set_list(stacks).ok("OK"))
 }

From 8fbf9d76de521704128657cb29921e0542f08238 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Mon, 18 Dec 2023 17:18:10 +0200
Subject: [PATCH 077/284] issue-auth routes/stack/add.rs

---
 src/routes/stack/add.rs | 15 ++++++---------
 1 file changed, 6 insertions(+), 9 deletions(-)

diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs
index 690a02a7..6bbae224 100644
--- a/src/routes/stack/add.rs
+++ b/src/routes/stack/add.rs
@@ -1,7 +1,6 @@
 use crate::forms::stack::StackForm;
 use crate::helpers::JsonResponse;
 use crate::models;
-use crate::models::user::User;
 use actix_web::post;
 use actix_web::{
     web,
@@ -21,20 +20,18 @@ use std::sync::Arc;
 #[post("")]
 pub async fn add(
     body: Bytes,
-    user: web::ReqData<Arc<User>>,
+    user: web::ReqData<Arc<models::User>>,
     pool: Data<PgPool>,
 ) -> Result<impl Responder> {
 
     // @todo ACL
     let body_bytes = actix_web::body::to_bytes(body).await.unwrap();
     let body_str = str::from_utf8(&body_bytes).unwrap();
-    let form = match serde_json::from_str::<StackForm>(body_str) {
-        Ok(f) => f,
-        Err(_err) => {
-            let msg = format!("Invalid data. {:?}", _err);
-            return Err(JsonResponse::<StackForm>::build().bad_request(msg));
-        }
-    };
+    let form = serde_json::from_str::<StackForm>(body_str)
+        .map_err(|err| {
+            let msg = format!("Invalid data. {:?}", err);
+            JsonResponse::<StackForm>::build().bad_request(msg)
+        })?;
 
     let stack_name = form.custom.custom_stack_code.clone();
     tracing::debug!("form before convert {:?}", form);

From 985a1a5102d2b933c7f425c499b8b62cc5e26987 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Mon, 18 Dec 2023 17:40:47 +0200
Subject: [PATCH 078/284] issue-auth src/routes/stack/add.rs

---
 src/db/stack.rs         | 29 +++++++++++++++++++++++++++++
 src/routes/stack/add.rs | 27 +++++++--------------------
 2 files changed, 36 insertions(+), 20 deletions(-)

diff --git a/src/db/stack.rs b/src/db/stack.rs
index 5cb2f92a..a6aeb598 100644
--- a/src/db/stack.rs
+++ b/src/db/stack.rs
@@ -49,3 +49,32 @@ pub async fn fetch_by_user(pool: &PgPool, user_id: String) -> Result<Vec<models:
         "".to_string()
     })
 }
+
+pub async fn fetch_one_by_name(pool: &PgPool, name: String) -> Result<Option<models::Stack>, String> {
+    let query_span = tracing::info_span!("Fetch one stack by name.");
+    sqlx::query_as!(
+        models::Stack,
+        r#"
+        SELECT
+            *
+        FROM user_stack
+        WHERE name=$1
+        LIMIT 1
+        "#,
+        name
+    )
+    .fetch_one(pool)
+    .instrument(query_span)
+    .await
+    .map(|stack| Some(stack))
+    .or_else(|err| {
+        match err {
+            sqlx::Error::RowNotFound => Ok(None),
+            err => {
+                tracing::error!("Failed to fetch one stack by name, error: {:?}", err);
+                Err("".to_string())
+            }
+
+        }
+    })
+}
diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs
index 6bbae224..2f12a2d9 100644
--- a/src/routes/stack/add.rs
+++ b/src/routes/stack/add.rs
@@ -1,8 +1,9 @@
 use crate::forms::stack::StackForm;
 use crate::helpers::JsonResponse;
 use crate::models;
-use actix_web::post;
+use crate::db;
 use actix_web::{
+    post,
     web,
     web::{Bytes, Data},
     Responder, Result,
@@ -34,28 +35,14 @@ pub async fn add(
         })?;
 
     let stack_name = form.custom.custom_stack_code.clone();
-    tracing::debug!("form before convert {:?}", form);
-
-    let query_span = tracing::info_span!("Check project/stack existence by custom_stack_code.");
-    match sqlx::query_as!(
-        models::Stack,
-        r"SELECT * FROM user_stack WHERE name = $1",
-        stack_name
-    )
-        .fetch_one(pool.get_ref())
-        .instrument(query_span)
-        .await
     {
-        Ok(record) => {
-            tracing::info!("record exists: id: {}, name: {}", record.id, record.name);
+        let stack = db::stack::fetch_one_by_name(pool.get_ref(), stack_name.clone())
+            .await
+            .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error("Internal Server Error"))?;
+        if stack.is_some() {
             return Err(JsonResponse::<models::Stack>::build().conflict("Stack with that name already exists"));
         }
-        Err(sqlx::Error::RowNotFound) => {}
-        Err(e) => {
-            tracing::error!("Failed to fetch stack info, error: {:?}", e);
-            return Err(JsonResponse::<models::Stack>::build().bad_request("Internal Server Error"));
-        }
-    };
+    }
 
     let user_id = user.id.clone();
     let request_id = Uuid::new_v4();

From fa8f86ec96c1bf53ec1d5c7528f785a9a4578885 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Mon, 18 Dec 2023 17:43:37 +0200
Subject: [PATCH 079/284] issue-auth src/routes/stack/add.rs

---
 src/db/stack.rs         | 4 ++--
 src/routes/stack/add.rs | 2 +-
 src/routes/stack/get.rs | 4 ++--
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/db/stack.rs b/src/db/stack.rs
index a6aeb598..3c0f7538 100644
--- a/src/db/stack.rs
+++ b/src/db/stack.rs
@@ -29,7 +29,7 @@ pub async fn fetch(pool: &PgPool, id: i32) -> Result<models::Stack, String> {
     })
 }
 
-pub async fn fetch_by_user(pool: &PgPool, user_id: String) -> Result<Vec<models::Stack>, String> {
+pub async fn fetch_by_user(pool: &PgPool, user_id: &str) -> Result<Vec<models::Stack>, String> {
     let query_span = tracing::info_span!("Fetch stacks by user id.");
     sqlx::query_as!(
         models::Stack,
@@ -50,7 +50,7 @@ pub async fn fetch_by_user(pool: &PgPool, user_id: String) -> Result<Vec<models:
     })
 }
 
-pub async fn fetch_one_by_name(pool: &PgPool, name: String) -> Result<Option<models::Stack>, String> {
+pub async fn fetch_one_by_name(pool: &PgPool, name: &str) -> Result<Option<models::Stack>, String> {
     let query_span = tracing::info_span!("Fetch one stack by name.");
     sqlx::query_as!(
         models::Stack,
diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs
index 2f12a2d9..8876a02c 100644
--- a/src/routes/stack/add.rs
+++ b/src/routes/stack/add.rs
@@ -36,7 +36,7 @@ pub async fn add(
 
     let stack_name = form.custom.custom_stack_code.clone();
     {
-        let stack = db::stack::fetch_one_by_name(pool.get_ref(), stack_name.clone())
+        let stack = db::stack::fetch_one_by_name(pool.get_ref(), &stack_name)
             .await
             .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error("Internal Server Error"))?;
         if stack.is_some() {
diff --git a/src/routes/stack/get.rs b/src/routes/stack/get.rs
index 7ac8fb50..b050720c 100644
--- a/src/routes/stack/get.rs
+++ b/src/routes/stack/get.rs
@@ -38,9 +38,9 @@ pub async fn list(
     /// This is admin endpoint, used by a m2m app, client app is confidential
     /// it should return stacks by user id
     /// in order to pass validation at external deployment service
-    let (user_id,) = path.into_inner();
+    let user_id = path.into_inner().0;
 
-    db::stack::fetch_by_user(pool.get_ref(), user_id) 
+    db::stack::fetch_by_user(pool.get_ref(), &user_id) 
         .await
         .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(""))
         .map(|stacks| JsonResponse::build().set_list(stacks).ok("OK"))

From 46aa43dcf97b56b14ff5d7c34653d1615e7cce7f Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Mon, 18 Dec 2023 19:01:44 +0200
Subject: [PATCH 080/284] issue-auth src/routes/stack/add.rs

---
 src/routes/stack/add.rs | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs
index 8876a02c..65b8c752 100644
--- a/src/routes/stack/add.rs
+++ b/src/routes/stack/add.rs
@@ -71,13 +71,11 @@ pub async fn add(
         return Err(JsonResponse::<models::Stack>::build().bad_request(errors.to_string()));// tmp solution
     }
 
-    let body: Value = match serde_json::to_value::<StackForm>(form) {
-        Ok(body) => body,
-        Err(err) => {
+    let body: Value = serde_json::to_value::<StackForm>(form)
+        .or_else(|err| {
             tracing::error!("Request_id {} error unwrap body {:?}", request_id, err);
-            serde_json::to_value::<StackForm>(StackForm::default()).unwrap()
-        }
-    };
+            Ok::<Value, Error>(serde_json::to_value::<StackForm>(StackForm::default()).unwrap())
+        }).unwrap();
 
     match sqlx::query!(
         r#"

From 295f36c5f6894d12b947f8b5e3270bf465a3003c Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Wed, 20 Dec 2023 17:14:10 +0200
Subject: [PATCH 081/284] issue-auth client db. api

---
 src/db/client.rs             | 9 +++++----
 src/routes/client/disable.rs | 4 +++-
 src/routes/client/enable.rs  | 4 +++-
 src/routes/client/update.rs  | 4 +++-
 src/routes/stack/add.rs      | 6 ++----
 5 files changed, 16 insertions(+), 11 deletions(-)

diff --git a/src/db/client.rs b/src/db/client.rs
index 477dbf5a..b8307a72 100644
--- a/src/db/client.rs
+++ b/src/db/client.rs
@@ -28,7 +28,7 @@ pub async fn update(pool: &PgPool, client: models::Client) -> Result<models::Cli
     })
 }
 
-pub async fn fetch(pool: &PgPool, id: i32) -> Result<models::Client, String> {
+pub async fn fetch(pool: &PgPool, id: i32) -> Result<Option<models::Client>, String> {
     let query_span = tracing::info_span!("Fetching the client by ID");
     sqlx::query_as!(
         models::Client,
@@ -46,12 +46,13 @@ pub async fn fetch(pool: &PgPool, id: i32) -> Result<models::Client, String> {
     .fetch_one(pool)
     .instrument(query_span)
     .await
-    .map_err(|e| {
+    .map(|client| Some(client))
+    .or_else(|e| {
         match e {
-            sqlx::Error::RowNotFound => "client not found".to_string(),
+            sqlx::Error::RowNotFound => Ok(None),
             s => {
                 tracing::error!("Failed to execute fetch query: {:?}", s);
-                "".to_string()
+                Err("".to_string())
             }
         }
     })
diff --git a/src/routes/client/disable.rs b/src/routes/client/disable.rs
index 2159febf..057a183f 100644
--- a/src/routes/client/disable.rs
+++ b/src/routes/client/disable.rs
@@ -18,7 +18,9 @@ pub async fn disable_handler(
         let client_id = path.0;
         let mut client = db::client::fetch(pool.get_ref(), client_id)
             .await
-            .map_err(|msg| JsonResponse::<models::Client>::build().not_found(msg))?; 
+            .map_err(|msg| JsonResponse::<models::Client>::build().internal_server_error(msg))?
+            .ok_or_else(|| JsonResponse::<models::Client>::build().not_found("not found"))?; 
+
         if client.secret.is_none() {
             return Err(JsonResponse::<models::Client>::build().bad_request("client is not active"));
         }
diff --git a/src/routes/client/enable.rs b/src/routes/client/enable.rs
index 6ed95d26..5669accc 100644
--- a/src/routes/client/enable.rs
+++ b/src/routes/client/enable.rs
@@ -19,7 +19,9 @@ pub async fn enable_handler(
     let client_id = path.0;
     let mut client = db::client::fetch(pool.get_ref(), client_id)
         .await
-        .map_err(|msg| JsonResponse::<models::Client>::build().not_found(msg))?; 
+        .map_err(|msg| JsonResponse::<models::Client>::build().internal_server_error(msg))?
+        .ok_or_else(|| JsonResponse::<models::Client>::build().not_found("not found"))?
+        ; 
 
     if client.secret.is_some() {
         return Err(JsonResponse::<models::Client>::build().bad_request("client is already active"));
diff --git a/src/routes/client/update.rs b/src/routes/client/update.rs
index 63b02a17..b1003162 100644
--- a/src/routes/client/update.rs
+++ b/src/routes/client/update.rs
@@ -19,7 +19,9 @@ pub async fn update_handler(
     let client_id = path.0;
     let mut client = db::client::fetch(pool.get_ref(), client_id)
         .await
-        .map_err(|msg| JsonResponse::<models::Client>::build().not_found(msg))?; 
+        .map_err(|msg| JsonResponse::<models::Client>::build().internal_server_error(msg))?
+        .ok_or_else(|| JsonResponse::<models::Client>::build().not_found("not found"))?; 
+
     if client.secret.is_none() {
         return Err(JsonResponse::<models::Client>::build().bad_request("client is not active"));
     }
diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs
index 65b8c752..dd744d2d 100644
--- a/src/routes/stack/add.rs
+++ b/src/routes/stack/add.rs
@@ -72,10 +72,8 @@ pub async fn add(
     }
 
     let body: Value = serde_json::to_value::<StackForm>(form)
-        .or_else(|err| {
-            tracing::error!("Request_id {} error unwrap body {:?}", request_id, err);
-            Ok::<Value, Error>(serde_json::to_value::<StackForm>(StackForm::default()).unwrap())
-        }).unwrap();
+        .or(serde_json::to_value::<StackForm>(StackForm::default()))
+        .unwrap();
 
     match sqlx::query!(
         r#"

From 7a3ca1a28543071e08f8e29892a6e906665e525b Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Wed, 20 Dec 2023 17:18:24 +0200
Subject: [PATCH 082/284] issue-auth product db api

---
 src/db/product.rs        | 9 +++++----
 src/routes/rating/add.rs | 4 +++-
 2 files changed, 8 insertions(+), 5 deletions(-)

diff --git a/src/db/product.rs b/src/db/product.rs
index 56431b1c..e9c591ae 100644
--- a/src/db/product.rs
+++ b/src/db/product.rs
@@ -2,7 +2,7 @@ use sqlx::PgPool;
 use crate::models;
 use tracing::Instrument;
 
-pub async fn fetch_by_obj(pg_pool: &PgPool, obj_id: i32) -> Result<models::Product, String> {
+pub async fn fetch_by_obj(pg_pool: &PgPool, obj_id: i32) -> Result<Option<models::Product>, String> {
     let query_span = tracing::info_span!("Check product existence by id.");
     sqlx::query_as!(
         models::Product,
@@ -17,12 +17,13 @@ pub async fn fetch_by_obj(pg_pool: &PgPool, obj_id: i32) -> Result<models::Produ
     .fetch_one(pg_pool)
     .instrument(query_span)
     .await
-    .map_err(|e|  {
+    .map(|product| Some(product))
+    .or_else(|e| {
         match e {
-            sqlx::Error::RowNotFound => "product not found".to_string(),
+            sqlx::Error::RowNotFound => Ok(None),
             s => {
                 tracing::error!("Failed to execute fetch query: {:?}", s);
-                "".to_string()
+                Err("".to_string())
             }
         }
     })
diff --git a/src/routes/rating/add.rs b/src/routes/rating/add.rs
index 88ec9b95..7ab543cf 100644
--- a/src/routes/rating/add.rs
+++ b/src/routes/rating/add.rs
@@ -22,7 +22,9 @@ pub async fn add_handler(
 ) -> Result<impl Responder> {
     let _product = db::product::fetch_by_obj(pg_pool.get_ref(), form.obj_id)
         .await
-        .map_err(|msg| JsonResponse::<models::Rating>::build().not_found(msg))?; 
+        .map_err(|msg| JsonResponse::<models::Rating>::build().internal_server_error(msg))?
+        .ok_or_else(|| JsonResponse::<models::Rating>::build().not_found("not found"))?
+        ; 
 
     match db::rating::fetch_by_obj_and_user_and_category(pg_pool.get_ref(), form.obj_id, user.id.clone(), form.category).await {
         Ok(record) => {

From da380cf4ce104df1f1c8c6ac9a7952c1a46deb8f Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Wed, 20 Dec 2023 17:22:55 +0200
Subject: [PATCH 083/284] issue-auth rating db api

---
 src/db/rating.rs         | 9 +++++----
 src/routes/rating/get.rs | 8 +++++---
 2 files changed, 10 insertions(+), 7 deletions(-)

diff --git a/src/db/rating.rs b/src/db/rating.rs
index e5cb40f7..d1b14552 100644
--- a/src/db/rating.rs
+++ b/src/db/rating.rs
@@ -27,7 +27,7 @@ pub async fn fetch_all(pool: &PgPool) -> Result<Vec<models::Rating>, String> {
     })
 }
 
-pub async fn fetch(pool: &PgPool, id: i32) -> Result<models::Rating, String> {
+pub async fn fetch(pool: &PgPool, id: i32) -> Result<Option<models::Rating>, String> {
     let query_span = tracing::info_span!("Fetch rating by id");
     sqlx::query_as!(
         models::Rating,
@@ -49,12 +49,13 @@ pub async fn fetch(pool: &PgPool, id: i32) -> Result<models::Rating, String> {
     .fetch_one(pool)
     .instrument(query_span)
     .await
-    .map_err(|e| {
+    .map(|rating| Some(rating))
+    .or_else(|e| {
         match e {
-            sqlx::Error::RowNotFound => "rating not found".to_string(),
+            sqlx::Error::RowNotFound => Ok(None),
             s => {
                 tracing::error!("Failed to execute fetch query: {:?}", s);
-                "".to_string()
+                Err("".to_string())
             }
         }
     })
diff --git a/src/routes/rating/get.rs b/src/routes/rating/get.rs
index 86c0e570..4bc0c1ff 100644
--- a/src/routes/rating/get.rs
+++ b/src/routes/rating/get.rs
@@ -17,10 +17,12 @@ pub async fn get_handler(
     pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
     let rate_id = path.0;
-    db::rating::fetch(pool.get_ref(), rate_id)
+    let rating = db::rating::fetch(pool.get_ref(), rate_id)
         .await
-        .map(|rating| JsonResponse::build().set_item(rating).ok("OK"))
-        .map_err(|err| JsonResponse::<models::Rating>::build().not_found("not found"))
+        .map_err(|_err| JsonResponse::<models::Rating>::build().internal_server_error(""))?
+        .ok_or_else(|| JsonResponse::<models::Rating>::build().not_found("not found"))?;
+
+    Ok(JsonResponse::build().set_item(rating).ok("OK"))
 }
 
 #[tracing::instrument(name = "Get all ratings.")]

From 0262171f093b1a8f4e6c1aa127dfbb92c0035c5b Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Thu, 21 Dec 2023 18:00:28 +0200
Subject: [PATCH 084/284] issue-auth
 db::rating::fetch_by_obj_and_user_and_category

---
 src/db/rating.rs         | 14 ++++++++++----
 src/routes/rating/add.rs | 10 +++++-----
 2 files changed, 15 insertions(+), 9 deletions(-)

diff --git a/src/db/rating.rs b/src/db/rating.rs
index d1b14552..599c49cc 100644
--- a/src/db/rating.rs
+++ b/src/db/rating.rs
@@ -61,7 +61,12 @@ pub async fn fetch(pool: &PgPool, id: i32) -> Result<Option<models::Rating>, Str
     })
 }
 
-pub async fn fetch_by_obj_and_user_and_category(pool: &PgPool, obj_id: i32, user_id: String, category: models::RateCategory) -> Result<models::Rating, String> {
+pub async fn fetch_by_obj_and_user_and_category(
+    pool: &PgPool,
+    obj_id: i32,
+    user_id: String,
+    category: models::RateCategory,
+) -> Result<Option<models::Rating>, String> {
     let query_span = tracing::info_span!("Fetch rating by obj, user and category.");
     sqlx::query_as!(
         models::Rating,
@@ -87,12 +92,13 @@ pub async fn fetch_by_obj_and_user_and_category(pool: &PgPool, obj_id: i32, user
     .fetch_one(pool)
     .instrument(query_span)
     .await
-    .map_err(|e| {
+    .map(|rating| Some(rating))
+    .or_else(|e| {
         match e {
-            sqlx::Error::RowNotFound => "fetch not found".to_string(),
+            sqlx::Error::RowNotFound => Ok(None),
             s => {
                 tracing::error!("Failed to execute fetch query: {:?}", s);
-                "".to_string()
+                Err("".to_string())
             }
         }
     })
diff --git a/src/routes/rating/add.rs b/src/routes/rating/add.rs
index 7ab543cf..48f03309 100644
--- a/src/routes/rating/add.rs
+++ b/src/routes/rating/add.rs
@@ -26,11 +26,11 @@ pub async fn add_handler(
         .ok_or_else(|| JsonResponse::<models::Rating>::build().not_found("not found"))?
         ; 
 
-    match db::rating::fetch_by_obj_and_user_and_category(pg_pool.get_ref(), form.obj_id, user.id.clone(), form.category).await {
-        Ok(record) => {
-            return Err(JsonResponse::<models::Rating>::build().conflict("Already rated"));
-        }
-        Err(_e) => {}
+    let rating = db::rating::fetch_by_obj_and_user_and_category(pg_pool.get_ref(), form.obj_id, user.id.clone(), form.category)
+        .await
+        .map_err(|err| JsonResponse::<models::Rating>::build().internal_server_error(err))?;
+    if rating.is_some() {
+        return Err(JsonResponse::<models::Rating>::build().bad_request("already rated"));
     }
 
     let mut rating: models::Rating = form.into_inner().into();

From a4998fc840de9ec430ada57ee26bc8e509dbc733 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Thu, 21 Dec 2023 18:06:48 +0200
Subject: [PATCH 085/284] issue-auth db::stack::fetch

---
 src/db/stack.rs         | 9 +++++----
 src/routes/stack/get.rs | 4 +++-
 2 files changed, 8 insertions(+), 5 deletions(-)

diff --git a/src/db/stack.rs b/src/db/stack.rs
index 3c0f7538..ec0970ba 100644
--- a/src/db/stack.rs
+++ b/src/db/stack.rs
@@ -2,7 +2,7 @@ use sqlx::PgPool;
 use crate::models;
 use tracing::Instrument;
 
-pub async fn fetch(pool: &PgPool, id: i32) -> Result<models::Stack, String> {
+pub async fn fetch(pool: &PgPool, id: i32) -> Result<Option<models::Stack>, String> {
     tracing::info!("Fecth stack {}", id);
     sqlx::query_as!(
         models::Stack,
@@ -17,12 +17,13 @@ pub async fn fetch(pool: &PgPool, id: i32) -> Result<models::Stack, String> {
     )
     .fetch_one(pool)
     .await
-    .map_err(|err| {
+    .map(|stack| Some(stack))
+    .or_else(|err| {
         match err {
-            sqlx::Error::RowNotFound => "".to_string(),
+            sqlx::Error::RowNotFound => Ok(None),
             e => {
                 tracing::error!("Failed to fetch stack, error: {:?}", e);
-                return "Could not fetch data".to_string();
+                Err("Could not fetch data".to_string())
             }
 
         }
diff --git a/src/routes/stack/get.rs b/src/routes/stack/get.rs
index b050720c..18a044c5 100644
--- a/src/routes/stack/get.rs
+++ b/src/routes/stack/get.rs
@@ -19,7 +19,9 @@ pub async fn item(
 
     let stack = db::stack::fetch(pool.get_ref(), id) 
         .await
-        .map_err(|err| JsonResponse::<models::Stack>::build().not_found("Record not found"))?;
+        .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(err))?
+        .ok_or_else(|| JsonResponse::<models::Stack>::build().not_found("not found"))?
+        ;
 
     if stack.user_id != user.id {
         return Err(JsonResponse::<models::Stack>::build().bad_request("Forbidden"));

From 2e6f5e390c68e80b50faad97234b5841ec3d2840 Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 23 Dec 2023 09:06:38 +0200
Subject: [PATCH 086/284] issue-auth db::stack::insert

---
 src/db/stack.rs         | 56 +++++++++++++++++++++++-----------
 src/models/stack.rs     | 26 ++++++++++++----
 src/routes/stack/add.rs | 66 ++++++++++++++---------------------------
 3 files changed, 82 insertions(+), 66 deletions(-)

diff --git a/src/db/stack.rs b/src/db/stack.rs
index ec0970ba..92725cdb 100644
--- a/src/db/stack.rs
+++ b/src/db/stack.rs
@@ -1,5 +1,5 @@
-use sqlx::PgPool;
 use crate::models;
+use sqlx::PgPool;
 use tracing::Instrument;
 
 pub async fn fetch(pool: &PgPool, id: i32) -> Result<Option<models::Stack>, String> {
@@ -18,14 +18,11 @@ pub async fn fetch(pool: &PgPool, id: i32) -> Result<Option<models::Stack>, Stri
     .fetch_one(pool)
     .await
     .map(|stack| Some(stack))
-    .or_else(|err| {
-        match err {
-            sqlx::Error::RowNotFound => Ok(None),
-            e => {
-                tracing::error!("Failed to fetch stack, error: {:?}", e);
-                Err("Could not fetch data".to_string())
-            }
-
+    .or_else(|err| match err {
+        sqlx::Error::RowNotFound => Ok(None),
+        e => {
+            tracing::error!("Failed to fetch stack, error: {:?}", e);
+            Err("Could not fetch data".to_string())
         }
     })
 }
@@ -68,14 +65,39 @@ pub async fn fetch_one_by_name(pool: &PgPool, name: &str) -> Result<Option<model
     .instrument(query_span)
     .await
     .map(|stack| Some(stack))
-    .or_else(|err| {
-        match err {
-            sqlx::Error::RowNotFound => Ok(None),
-            err => {
-                tracing::error!("Failed to fetch one stack by name, error: {:?}", err);
-                Err("".to_string())
-            }
-
+    .or_else(|err| match err {
+        sqlx::Error::RowNotFound => Ok(None),
+        err => {
+            tracing::error!("Failed to fetch one stack by name, error: {:?}", err);
+            Err("".to_string())
         }
     })
 }
+
+pub async fn insert(pool: &PgPool, mut stack: models::Stack) -> Result<models::Stack, String> {
+    let query_span = tracing::info_span!("Saving new stack into the database");
+    sqlx::query!(
+        r#"
+        INSERT INTO user_stack (stack_id, user_id, name, body, created_at, updated_at)
+        VALUES ($1, $2, $3, $4, $5, $6)
+        RETURNING id;
+        "#,
+        stack.stack_id,
+        stack.user_id,
+        stack.name,
+        stack.body,
+        stack.created_at,
+        stack.updated_at,
+    )
+    .fetch_one(pool)
+    .instrument(query_span)
+    .await
+    .map(move |result| {
+        stack.id = result.id;
+        stack
+    })
+    .map_err(|e| {
+        tracing::error!("Failed to execute query: {:?}", e);
+        "Failed to insert".to_string()
+    })
+}
diff --git a/src/models/stack.rs b/src/models/stack.rs
index f9ce272e..a808f130 100644
--- a/src/models/stack.rs
+++ b/src/models/stack.rs
@@ -1,13 +1,13 @@
 use chrono::{DateTime, Utc};
+use serde::{Deserialize, Serialize};
 use serde_json::Value;
 use uuid::Uuid;
-use serde::{Serialize,Deserialize};
 
 #[derive(Debug, Serialize, Deserialize, Clone)]
 pub struct Stack {
-    pub id: i32,       // id - is a unique identifier for the app stack
-    pub stack_id: Uuid, // external stack ID
-    pub user_id: String,  // external unique identifier for the user
+    pub id: i32,         // id - is a unique identifier for the app stack
+    pub stack_id: Uuid,  // external stack ID
+    pub user_id: String, // external unique identifier for the user
     pub name: String,
     // pub body: sqlx::types::Json<String>,
     pub body: Value, //json type
@@ -15,12 +15,26 @@ pub struct Stack {
     pub updated_at: DateTime<Utc>,
 }
 
+impl Stack {
+    pub fn new(user_id: String, name: String, body: Value) -> Self {
+        Self {
+            id: 0,
+            stack_id: Uuid::new_v4(),
+            user_id: user_id,
+            name: name,
+            body: body,
+            created_at: Utc::now(),
+            updated_at: Utc::now(),
+        }
+    }
+}
+
 impl Default for Stack {
     fn default() -> Self {
         Stack {
             user_id: "".to_string(),
             name: "".to_string(),
-                ..Default::default()
+            ..Default::default()
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs
index dd744d2d..c08255f5 100644
--- a/src/routes/stack/add.rs
+++ b/src/routes/stack/add.rs
@@ -1,21 +1,20 @@
+use crate::db;
 use crate::forms::stack::StackForm;
 use crate::helpers::JsonResponse;
 use crate::models;
-use crate::db;
 use actix_web::{
-    post,
-    web,
+    post, web,
     web::{Bytes, Data},
     Responder, Result,
 };
 use chrono::Utc;
 use serde_json::Value;
+use serde_valid::Validate;
 use sqlx::PgPool;
 use std::str;
-use serde_valid::Validate;
+use std::sync::Arc;
 use tracing::Instrument;
 use uuid::Uuid;
-use std::sync::Arc;
 
 #[tracing::instrument(name = "Add stack.")]
 #[post("")]
@@ -24,23 +23,25 @@ pub async fn add(
     user: web::ReqData<Arc<models::User>>,
     pool: Data<PgPool>,
 ) -> Result<impl Responder> {
-
     // @todo ACL
     let body_bytes = actix_web::body::to_bytes(body).await.unwrap();
     let body_str = str::from_utf8(&body_bytes).unwrap();
-    let form = serde_json::from_str::<StackForm>(body_str)
-        .map_err(|err| {
-            let msg = format!("Invalid data. {:?}", err);
-            JsonResponse::<StackForm>::build().bad_request(msg)
-        })?;
+    let form = serde_json::from_str::<StackForm>(body_str).map_err(|err| {
+        let msg = format!("Invalid data. {:?}", err);
+        JsonResponse::<StackForm>::build().bad_request(msg)
+    })?;
 
     let stack_name = form.custom.custom_stack_code.clone();
     {
         let stack = db::stack::fetch_one_by_name(pool.get_ref(), &stack_name)
             .await
-            .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error("Internal Server Error"))?;
+            .map_err(|err| {
+                JsonResponse::<models::Stack>::build()
+                    .internal_server_error("Internal Server Error")
+            })?;
         if stack.is_some() {
-            return Err(JsonResponse::<models::Stack>::build().conflict("Stack with that name already exists"));
+            return Err(JsonResponse::<models::Stack>::build()
+                .conflict("Stack with that name already exists"));
         }
     }
 
@@ -68,40 +69,19 @@ pub async fn add(
         let errors = form.validate().unwrap_err();
         let err_msg = format!("Invalid data received {:?}", &errors.to_string());
         tracing::debug!(err_msg);
-        return Err(JsonResponse::<models::Stack>::build().bad_request(errors.to_string()));// tmp solution
+        return Err(JsonResponse::<models::Stack>::build().bad_request(errors.to_string()));
+        // tmp solution
     }
 
     let body: Value = serde_json::to_value::<StackForm>(form)
         .or(serde_json::to_value::<StackForm>(StackForm::default()))
         .unwrap();
 
-    match sqlx::query!(
-        r#"
-        INSERT INTO user_stack (stack_id, user_id, name, body, created_at, updated_at)
-        VALUES ($1, $2, $3, $4, $5, $6)
-        RETURNING id;
-        "#,
-        Uuid::new_v4(),
-        user_id,
-        stack_name,
-        body,
-        Utc::now(),
-        Utc::now(),
-    )
-    .fetch_one(pool.get_ref())
-    .instrument(query_span)
-    .await
-    {
-        Ok(record) => {
-            tracing::info!(
-                "req_id: {} New stack details have been saved to database",
-                request_id
-            );
-            return Ok(JsonResponse::<models::Stack>::build().set_id(record.id).ok("OK"));
-        }
-        Err(e) => {
-            tracing::error!("req_id: {} Failed to execute query: {:?}", request_id, e);
-            return Err(JsonResponse::<models::Stack>::build().internal_server_error("Internal Server Error"));
-        }
-    };
+    let stack = models::Stack::new(user_id, stack_name, body);
+    db::stack::insert(pool.get_ref(), stack)
+        .await
+        .map(|stack| JsonResponse::build().set_item(stack).ok("Ok"))
+        .map_err(|_| {
+            JsonResponse::<models::Stack>::build().internal_server_error("Internal Server Error")
+        })
 }

From 4f13c4cf9ddb1adb916769b367b1a92e6884dc8b Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 23 Dec 2023 09:09:23 +0200
Subject: [PATCH 087/284] issue-auth removed tracings

---
 src/routes/stack/add.rs | 22 +---------------------
 1 file changed, 1 insertion(+), 21 deletions(-)

diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs
index c08255f5..2db892b8 100644
--- a/src/routes/stack/add.rs
+++ b/src/routes/stack/add.rs
@@ -45,26 +45,6 @@ pub async fn add(
         }
     }
 
-    let user_id = user.id.clone();
-    let request_id = Uuid::new_v4();
-    let request_span = tracing::info_span!(
-        "Validating a new stack", %request_id,
-        commonDomain=?&form.custom.project_name,
-        region=?&form.region,
-        domainList=?&form.domain_list
-    );
-    // using `enter` is an async function
-    let _request_span_guard = request_span.enter(); // ->exit
-
-    tracing::info!(
-        "request_id {} Adding '{}' '{}' as a new stack",
-        request_id,
-        form.custom.project_name,
-        form.region
-    );
-
-    let query_span = tracing::info_span!("Saving new stack details into the database");
-
     if !form.validate().is_ok() {
         let errors = form.validate().unwrap_err();
         let err_msg = format!("Invalid data received {:?}", &errors.to_string());
@@ -77,7 +57,7 @@ pub async fn add(
         .or(serde_json::to_value::<StackForm>(StackForm::default()))
         .unwrap();
 
-    let stack = models::Stack::new(user_id, stack_name, body);
+    let stack = models::Stack::new(user.id.clone(), stack_name, body);
     db::stack::insert(pool.get_ref(), stack)
         .await
         .map(|stack| JsonResponse::build().set_item(stack).ok("Ok"))

From a10eca7d6691ba9ec5665b578959007eec587f70 Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 23 Dec 2023 09:29:08 +0200
Subject: [PATCH 088/284] issue-auth src/routes/stack.add.rs internal server
 error

---
 src/routes/stack/add.rs | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs
index 2db892b8..9757f225 100644
--- a/src/routes/stack/add.rs
+++ b/src/routes/stack/add.rs
@@ -7,14 +7,11 @@ use actix_web::{
     web::{Bytes, Data},
     Responder, Result,
 };
-use chrono::Utc;
 use serde_json::Value;
 use serde_valid::Validate;
 use sqlx::PgPool;
 use std::str;
 use std::sync::Arc;
-use tracing::Instrument;
-use uuid::Uuid;
 
 #[tracing::instrument(name = "Add stack.")]
 #[post("")]
@@ -25,7 +22,8 @@ pub async fn add(
 ) -> Result<impl Responder> {
     // @todo ACL
     let body_bytes = actix_web::body::to_bytes(body).await.unwrap();
-    let body_str = str::from_utf8(&body_bytes).unwrap();
+    let body_str = str::from_utf8(&body_bytes)
+        .map_err(|err| JsonResponse::<StackForm>::build().internal_server_error(err.to_string()))?;
     let form = serde_json::from_str::<StackForm>(body_str).map_err(|err| {
         let msg = format!("Invalid data. {:?}", err);
         JsonResponse::<StackForm>::build().bad_request(msg)

From e569835d7b0c1615dd38dd0b1584b31f8b5db248 Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 23 Dec 2023 09:47:15 +0200
Subject: [PATCH 089/284] issue-auth check_if_stack_exists

---
 src/routes/stack/add.rs | 27 +++++++++++++++------------
 1 file changed, 15 insertions(+), 12 deletions(-)

diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs
index 9757f225..71d73b28 100644
--- a/src/routes/stack/add.rs
+++ b/src/routes/stack/add.rs
@@ -2,6 +2,7 @@ use crate::db;
 use crate::forms::stack::StackForm;
 use crate::helpers::JsonResponse;
 use crate::models;
+use actix_web::Error;
 use actix_web::{
     post, web,
     web::{Bytes, Data},
@@ -30,18 +31,7 @@ pub async fn add(
     })?;
 
     let stack_name = form.custom.custom_stack_code.clone();
-    {
-        let stack = db::stack::fetch_one_by_name(pool.get_ref(), &stack_name)
-            .await
-            .map_err(|err| {
-                JsonResponse::<models::Stack>::build()
-                    .internal_server_error("Internal Server Error")
-            })?;
-        if stack.is_some() {
-            return Err(JsonResponse::<models::Stack>::build()
-                .conflict("Stack with that name already exists"));
-        }
-    }
+    check_if_stack_exists(pool.get_ref(), &stack_name).await?;
 
     if !form.validate().is_ok() {
         let errors = form.validate().unwrap_err();
@@ -63,3 +53,16 @@ pub async fn add(
             JsonResponse::<models::Stack>::build().internal_server_error("Internal Server Error")
         })
 }
+
+async fn check_if_stack_exists(pool: &PgPool, stack_name: &String) -> Result<(), Error> {
+    db::stack::fetch_one_by_name(pool, stack_name)
+        .await
+        .map_err(|_| {
+            JsonResponse::<models::Stack>::build().internal_server_error("Internal Server Error")
+        })
+        .and_then(|stack| match stack {
+            Some(_) => Err(JsonResponse::<models::Stack>::build()
+                .conflict("Stack with that name already exists")),
+            None => Ok(()),
+        })
+}

From 32b571dc64d49cedb7cd74955ac3fed580d43a81 Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 23 Dec 2023 09:55:49 +0200
Subject: [PATCH 090/284] issue-auth convert body to form

---
 src/forms/stack.rs      | 157 +++++++++++++++++++---------------------
 src/routes/stack/add.rs |  20 +++--
 2 files changed, 88 insertions(+), 89 deletions(-)

diff --git a/src/forms/stack.rs b/src/forms/stack.rs
index b67a8566..cc322330 100644
--- a/src/forms/stack.rs
+++ b/src/forms/stack.rs
@@ -1,10 +1,9 @@
-use std::collections::HashMap;
 use serde::{Deserialize, Serialize};
 use serde_json::Value;
 use serde_valid::Validate;
+use std::collections::HashMap;
 use std::fmt;
 
-
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Role {
     pub role: Option<Vec<String>>,
@@ -12,18 +11,18 @@ pub struct Role {
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Requirements {
-    #[validate(min_length=1)]
-    #[validate(max_length=10)]
+    #[validate(min_length = 1)]
+    #[validate(max_length = 10)]
     #[validate(pattern = r"^\d+\.?[0-9]+$")]
     pub cpu: Option<String>,
-    #[validate(min_length=1)]
-    #[validate(max_length=10)]
+    #[validate(min_length = 1)]
+    #[validate(max_length = 10)]
     #[validate(pattern = r"^\d+G$")]
     #[serde(rename = "disk_size")]
     pub disk_size: Option<String>,
     #[serde(rename = "ram_size")]
-    #[validate(min_length=1)]
-    #[validate(max_length=10)]
+    #[validate(min_length = 1)]
+    #[validate(max_length = 10)]
     #[validate(pattern = r"^\d+G$")]
     pub ram_size: Option<String>,
 }
@@ -31,7 +30,7 @@ pub struct Requirements {
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct Port {
     pub host_port: Option<String>,
-    pub container_port: Option<String>
+    pub container_port: Option<String>,
 }
 
 // #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
@@ -45,27 +44,31 @@ pub struct Port {
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct DockerImage {
-    #[validate(min_length=3)]
-    #[validate(max_length=50)]
+    #[validate(min_length = 3)]
+    #[validate(max_length = 50)]
     pub dockerhub_user: Option<String>,
-    #[validate(min_length=3)]
-    #[validate(max_length=50)]
+    #[validate(min_length = 3)]
+    #[validate(max_length = 50)]
     pub dockerhub_name: Option<String>,
-    #[validate(min_length=3)]
-    #[validate(max_length=100)]
+    #[validate(min_length = 3)]
+    #[validate(max_length = 100)]
     pub dockerhub_image: Option<String>,
 }
 
-impl fmt::Display for DockerImage
-{
+impl fmt::Display for DockerImage {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         let tag = "latest";
 
-        let dim = self.dockerhub_image.clone()
-            .unwrap_or("".to_string());
-        write!(f, "{}/{}:{}", self.dockerhub_user.clone()
-            .unwrap_or("trydirect".to_string()).clone(),
-                self.dockerhub_name.clone().unwrap_or(dim), tag
+        let dim = self.dockerhub_image.clone().unwrap_or("".to_string());
+        write!(
+            f,
+            "{}/{}:{}",
+            self.dockerhub_user
+                .clone()
+                .unwrap_or("trydirect".to_string())
+                .clone(),
+            self.dockerhub_name.clone().unwrap_or(dim),
+            tag
         )
     }
 }
@@ -76,61 +79,59 @@ impl AsRef<DockerImage> for App {
     }
 }
 
-
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct StackForm {
     // #[validate(min_length=2)]
     // #[validate(max_length=255)]
-    #[serde(rename= "commonDomain")]
+    #[serde(rename = "commonDomain")]
     pub common_domain: Option<String>,
     pub domain_list: Option<DomainList>,
-    #[validate(min_length=2)]
-    #[validate(max_length=255)]
+    #[validate(min_length = 2)]
+    #[validate(max_length = 255)]
     pub stack_code: Option<String>,
-    #[validate(min_length=2)]
-    #[validate(max_length=50)]
+    #[validate(min_length = 2)]
+    #[validate(max_length = 50)]
     pub region: String,
-    #[validate(min_length=2)]
-    #[validate(max_length=50)]
+    #[validate(min_length = 2)]
+    #[validate(max_length = 50)]
     pub zone: Option<String>,
-    #[validate(min_length=2)]
-    #[validate(max_length=50)]
+    #[validate(min_length = 2)]
+    #[validate(max_length = 50)]
     pub server: String,
-    #[validate(min_length=2)]
-    #[validate(max_length=50)]
+    #[validate(min_length = 2)]
+    #[validate(max_length = 50)]
     pub os: String,
-    #[validate(min_length=3)]
-    #[validate(max_length=50)]
+    #[validate(min_length = 3)]
+    #[validate(max_length = 50)]
     pub ssl: String,
     pub vars: Option<Vec<Var>>,
     pub integrated_features: Option<Vec<Value>>,
     pub extended_features: Option<Vec<Value>>,
     pub subscriptions: Option<Vec<String>>,
     pub form_app: Option<Vec<String>>,
-    #[validate(min_length=3)]
-    #[validate(max_length=50)]
+    #[validate(min_length = 3)]
+    #[validate(max_length = 50)]
     pub disk_type: Option<String>,
     pub save_token: bool,
-    #[validate(min_length=10)]
-    #[validate(max_length=255)]
+    #[validate(min_length = 10)]
+    #[validate(max_length = 255)]
     pub cloud_token: String,
-    #[validate(min_length=2)]
-    #[validate(max_length=50)]
+    #[validate(min_length = 2)]
+    #[validate(max_length = 50)]
     pub provider: String,
-    #[validate(min_length=3)]
-    #[validate(max_length=50)]
+    #[validate(min_length = 3)]
+    #[validate(max_length = 50)]
     pub selected_plan: String,
     pub custom: Custom,
 }
 
-
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 #[serde(rename_all = "snake_case")]
 pub struct StackPayload {
     pub(crate) id: Option<i32>,
     pub(crate) user_token: Option<String>,
     pub(crate) user_email: Option<String>,
-    #[serde(rename= "commonDomain")]
+    #[serde(rename = "commonDomain")]
     pub common_domain: String,
     pub domain_list: Option<DomainList>,
     pub region: String,
@@ -159,17 +160,15 @@ pub struct StackPayload {
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 #[serde(rename_all = "camelCase")]
-pub struct DomainList {
-}
+pub struct DomainList {}
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 #[serde(rename_all = "camelCase")]
-pub struct Var {
-}
+pub struct Var {}
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct Price {
-    pub value: f64
+    pub value: f64,
 }
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Custom {
@@ -179,17 +178,17 @@ pub struct Custom {
     #[validate(minimum = 0)]
     #[validate(maximum = 10)]
     pub servers_count: u32,
-    #[validate(min_length=3)]
-    #[validate(max_length=50)]
+    #[validate(min_length = 3)]
+    #[validate(max_length = 50)]
     pub custom_stack_code: String,
-    #[validate(min_length=3)]
-    #[validate(max_length=255)]
+    #[validate(min_length = 3)]
+    #[validate(max_length = 255)]
     pub project_git_url: Option<String>,
     pub custom_stack_category: Option<Vec<String>>,
     pub custom_stack_short_description: Option<String>,
     pub custom_stack_description: Option<String>,
-    #[validate(min_length=3)]
-    #[validate(max_length=255)]
+    #[validate(min_length = 3)]
+    #[validate(max_length = 255)]
     pub project_name: String,
     pub project_overview: Option<String>,
     pub project_description: Option<String>,
@@ -199,15 +198,14 @@ pub struct Custom {
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Network {
-    name: String
+    name: String,
 }
 
-
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct App {
     #[serde(rename = "_etag")]
-    #[validate(min_length=3)]
-    #[validate(max_length=255)]
+    #[validate(min_length = 3)]
+    #[validate(max_length = 255)]
     pub etag: Option<String>,
     #[serde(rename = "_id")]
     pub id: u32,
@@ -215,14 +213,14 @@ pub struct App {
     pub created: Option<String>,
     #[serde(rename = "_updated")]
     pub updated: Option<String>,
-    #[validate(min_length=3)]
-    #[validate(max_length=50)]
+    #[validate(min_length = 3)]
+    #[validate(max_length = 50)]
     pub name: String,
-    #[validate(min_length=3)]
-    #[validate(max_length=50)]
+    #[validate(min_length = 3)]
+    #[validate(max_length = 50)]
     pub code: String,
-    #[validate(min_length=3)]
-    #[validate(max_length=50)]
+    #[validate(min_length = 3)]
+    #[validate(max_length = 50)]
     #[serde(rename = "type")]
     pub type_field: String,
     #[serde(flatten)]
@@ -233,7 +231,7 @@ pub struct App {
     pub docker_image: DockerImage,
     #[serde(flatten)]
     pub requirements: Requirements,
-    #[validate(minimum=1)]
+    #[validate(minimum = 1)]
     pub popularity: Option<u32>,
     pub commercial: Option<bool>,
     pub subscription: Option<Value>,
@@ -271,28 +269,28 @@ pub struct App {
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct Environment {
-    pub(crate) environment: Option<Vec<HashMap<String, String>>>
+    pub(crate) environment: Option<Vec<HashMap<String, String>>>,
 }
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct Volume {
     pub(crate) host_path: Option<String>,
-    pub(crate) container_path: Option<String>
+    pub(crate) container_path: Option<String>,
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct Volumes {
-    volumes: Vec<Volume>
+    volumes: Vec<Volume>,
 }
 
 // pub(crate) type Networks = Option<Vec<String>>;
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct ServiceNetworks {
-    pub network: Option<Vec<String>>
+    pub network: Option<Vec<String>>,
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct ComposeNetworks {
-    pub networks: Option<Vec<String>>
+    pub networks: Option<Vec<String>>,
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
@@ -337,8 +335,7 @@ pub struct IconLight {
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct IconDark {
-}
+pub struct IconDark {}
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Version {
@@ -352,14 +349,12 @@ pub struct Version {
     pub updated: Option<String>,
     pub app_id: Option<u32>,
     pub name: String,
-    #[validate(min_length=3)]
-    #[validate(max_length=20)]
+    #[validate(min_length = 3)]
+    #[validate(max_length = 20)]
     pub version: String,
     #[serde(rename = "update_status")]
     pub update_status: Option<String>,
-    #[validate(min_length=3)]
-    #[validate(max_length=20)]
+    #[validate(min_length = 3)]
+    #[validate(max_length = 20)]
     pub tag: String,
 }
-
-
diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs
index 71d73b28..4c940c8c 100644
--- a/src/routes/stack/add.rs
+++ b/src/routes/stack/add.rs
@@ -22,15 +22,9 @@ pub async fn add(
     pool: Data<PgPool>,
 ) -> Result<impl Responder> {
     // @todo ACL
-    let body_bytes = actix_web::body::to_bytes(body).await.unwrap();
-    let body_str = str::from_utf8(&body_bytes)
-        .map_err(|err| JsonResponse::<StackForm>::build().internal_server_error(err.to_string()))?;
-    let form = serde_json::from_str::<StackForm>(body_str).map_err(|err| {
-        let msg = format!("Invalid data. {:?}", err);
-        JsonResponse::<StackForm>::build().bad_request(msg)
-    })?;
-
+    let form = convert_body_to_form(body).await?;
     let stack_name = form.custom.custom_stack_code.clone();
+
     check_if_stack_exists(pool.get_ref(), &stack_name).await?;
 
     if !form.validate().is_ok() {
@@ -66,3 +60,13 @@ async fn check_if_stack_exists(pool: &PgPool, stack_name: &String) -> Result<(),
             None => Ok(()),
         })
 }
+
+async fn convert_body_to_form(body: Bytes) -> Result<StackForm, Error> {
+    let body_bytes = actix_web::body::to_bytes(body).await.unwrap();
+    let body_str = str::from_utf8(&body_bytes)
+        .map_err(|err| JsonResponse::<StackForm>::build().internal_server_error(err.to_string()))?;
+    serde_json::from_str::<StackForm>(body_str).map_err(|err| {
+        let msg = format!("Invalid data. {:?}", err);
+        JsonResponse::<StackForm>::build().bad_request(msg)
+    })
+}

From 0626be0384021a0408ad5960f2ad1ff1de24ac0e Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 23 Dec 2023 10:08:57 +0200
Subject: [PATCH 091/284] issue-auth body_to_form

---
 src/routes/stack/add.rs | 31 +++++++++++++++++--------------
 1 file changed, 17 insertions(+), 14 deletions(-)

diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs
index 4c940c8c..ec9c806d 100644
--- a/src/routes/stack/add.rs
+++ b/src/routes/stack/add.rs
@@ -22,19 +22,11 @@ pub async fn add(
     pool: Data<PgPool>,
 ) -> Result<impl Responder> {
     // @todo ACL
-    let form = convert_body_to_form(body).await?;
+    let form = body_into_form(body).await?;
     let stack_name = form.custom.custom_stack_code.clone();
 
     check_if_stack_exists(pool.get_ref(), &stack_name).await?;
 
-    if !form.validate().is_ok() {
-        let errors = form.validate().unwrap_err();
-        let err_msg = format!("Invalid data received {:?}", &errors.to_string());
-        tracing::debug!(err_msg);
-        return Err(JsonResponse::<models::Stack>::build().bad_request(errors.to_string()));
-        // tmp solution
-    }
-
     let body: Value = serde_json::to_value::<StackForm>(form)
         .or(serde_json::to_value::<StackForm>(StackForm::default()))
         .unwrap();
@@ -61,12 +53,23 @@ async fn check_if_stack_exists(pool: &PgPool, stack_name: &String) -> Result<(),
         })
 }
 
-async fn convert_body_to_form(body: Bytes) -> Result<StackForm, Error> {
+async fn body_into_form(body: Bytes) -> Result<StackForm, Error> {
     let body_bytes = actix_web::body::to_bytes(body).await.unwrap();
     let body_str = str::from_utf8(&body_bytes)
         .map_err(|err| JsonResponse::<StackForm>::build().internal_server_error(err.to_string()))?;
-    serde_json::from_str::<StackForm>(body_str).map_err(|err| {
-        let msg = format!("Invalid data. {:?}", err);
-        JsonResponse::<StackForm>::build().bad_request(msg)
-    })
+    serde_json::from_str::<StackForm>(body_str)
+        .map_err(|err| {
+            let msg = format!("Invalid data. {:?}", err);
+            JsonResponse::<StackForm>::build().bad_request(msg)
+        })
+        .and_then(|form| {
+            if !form.validate().is_ok() {
+                let errors = form.validate().unwrap_err();
+                let err_msg = format!("Invalid data received {:?}", &errors.to_string());
+                tracing::debug!(err_msg);
+                return Err(JsonResponse::<models::Stack>::build().bad_request(errors.to_string()));
+            }
+
+            Ok(form)
+        })
 }

From 63fa86b77ccb97ec27e2bb1885ae885bf63aef8c Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 23 Dec 2023 18:03:13 +0200
Subject: [PATCH 092/284] issue-auth json error path

---
 Cargo.lock              | 11 +++++++++++
 Cargo.toml              |  1 +
 src/routes/stack/add.rs | 18 ++++++++++++++++++
 3 files changed, 30 insertions(+)

diff --git a/Cargo.lock b/Cargo.lock
index 6c5834b6..73e0b28a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2803,6 +2803,16 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "serde_path_to_error"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335"
+dependencies = [
+ "itoa",
+ "serde",
+]
+
 [[package]]
 name = "serde_qs"
 version = "0.8.5"
@@ -3107,6 +3117,7 @@ dependencies = [
  "serde",
  "serde_derive",
  "serde_json",
+ "serde_path_to_error",
  "serde_valid",
  "serde_yaml",
  "sha2",
diff --git a/Cargo.toml b/Cargo.toml
index b23bf3b2..54ee40f6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -52,6 +52,7 @@ serde_yaml = "0.9"
 lapin = { version = "2.3.1", features = ["serde_json"] }
 futures-lite = "1.13.0"
 clap = { version = "4.4.8", features = ["derive"] }
+serde_path_to_error = "0.1.14"
 
 [dependencies.sqlx]
 version = "0.6.3"
diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs
index ec9c806d..b8c3e26a 100644
--- a/src/routes/stack/add.rs
+++ b/src/routes/stack/add.rs
@@ -57,6 +57,23 @@ async fn body_into_form(body: Bytes) -> Result<StackForm, Error> {
     let body_bytes = actix_web::body::to_bytes(body).await.unwrap();
     let body_str = str::from_utf8(&body_bytes)
         .map_err(|err| JsonResponse::<StackForm>::build().internal_server_error(err.to_string()))?;
+    let deserializer = &mut serde_json::Deserializer::from_str(body_str);
+    serde_path_to_error::deserialize(deserializer)
+        .map_err(|err| {
+            let msg = format!("{}:{:?}", err.path().to_string(), err);
+            JsonResponse::<StackForm>::build().bad_request(msg)
+        })
+        .and_then(|form: StackForm| {
+            if !form.validate().is_ok() {
+                let errors = form.validate().unwrap_err();
+                let err_msg = format!("Invalid data received {:?}", &errors.to_string());
+                tracing::debug!(err_msg);
+                return Err(JsonResponse::<models::Stack>::build().bad_request(errors.to_string()));
+            }
+
+            Ok(form)
+        })
+    /*
     serde_json::from_str::<StackForm>(body_str)
         .map_err(|err| {
             let msg = format!("Invalid data. {:?}", err);
@@ -72,4 +89,5 @@ async fn body_into_form(body: Bytes) -> Result<StackForm, Error> {
 
             Ok(form)
         })
+    */
 }

From 623e5ddd012a9d1ea52973853a24be5af1f3dd6a Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 23 Dec 2023 18:05:35 +0200
Subject: [PATCH 093/284] issue-auth removed comments

---
 src/routes/stack/add.rs | 17 -----------------
 1 file changed, 17 deletions(-)

diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs
index b8c3e26a..ef38ac5f 100644
--- a/src/routes/stack/add.rs
+++ b/src/routes/stack/add.rs
@@ -73,21 +73,4 @@ async fn body_into_form(body: Bytes) -> Result<StackForm, Error> {
 
             Ok(form)
         })
-    /*
-    serde_json::from_str::<StackForm>(body_str)
-        .map_err(|err| {
-            let msg = format!("Invalid data. {:?}", err);
-            JsonResponse::<StackForm>::build().bad_request(msg)
-        })
-        .and_then(|form| {
-            if !form.validate().is_ok() {
-                let errors = form.validate().unwrap_err();
-                let err_msg = format!("Invalid data received {:?}", &errors.to_string());
-                tracing::debug!(err_msg);
-                return Err(JsonResponse::<models::Stack>::build().bad_request(errors.to_string()));
-            }
-
-            Ok(form)
-        })
-    */
 }

From a197ba53de7796fa768027e47acb1b72a6a9e1e6 Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 23 Dec 2023 18:28:14 +0200
Subject: [PATCH 094/284] issue-auth stack/compose.rs add

---
 src/routes/stack/compose.rs | 92 +++++++++++--------------------------
 1 file changed, 28 insertions(+), 64 deletions(-)

diff --git a/src/routes/stack/compose.rs b/src/routes/stack/compose.rs
index b92930b0..508d2341 100644
--- a/src/routes/stack/compose.rs
+++ b/src/routes/stack/compose.rs
@@ -1,77 +1,46 @@
-use actix_web::{
-    web,
-    web::{Data, Json},
-    Responder, Result,
-};
+use actix_web::{get, web, web::Data, Responder, Result};
 
+use crate::db;
 use crate::helpers::stack::builder::DcBuilder;
 use crate::helpers::JsonResponse;
-use crate::models::user::User;
-use crate::models::Stack;
-use actix_web::{get, post};
+use crate::models;
 use sqlx::PgPool;
-use std::str;
-use tracing::Instrument;
 use std::sync::Arc;
 
 #[tracing::instrument(name = "User's generate docker-compose.")]
 #[get("/{id}")]
 pub async fn add(
-    user: web::ReqData<Arc<User>>,
+    user: web::ReqData<Arc<models::User>>,
     path: web::Path<(i32,)>,
     pool: Data<PgPool>,
 ) -> Result<impl Responder> {
     let id = path.0;
-    tracing::debug!("Received id: {}", id);
-
-    let stack = match sqlx::query_as!(
-        Stack,
-        r#"
-        SELECT * FROM user_stack WHERE id=$1 AND user_id=$2 LIMIT 1
-        "#,
-        id,
-        user.id
-    )
-    .fetch_one(pool.get_ref())
-    .await
-    {
-        Ok(stack) => {
-            tracing::info!("stack found: {:?}", stack.id);
-            Some(stack)
-        }
-        Err(sqlx::Error::RowNotFound) => {
-            tracing::error!("Row not found 404");
-            None
-        }
-        Err(e) => {
-            tracing::error!("Failed to fetch stack, error: {:?}", e);
-            None
-        }
-    };
+    let stack = db::stack::fetch(pool.get_ref(), id)
+        .await
+        .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(err))
+        .and_then(|stack| match stack {
+            Some(stack) if stack.user_id != user.id => {
+                Err(JsonResponse::<models::Stack>::build().not_found("not found"))
+            }
+            Some(stack) => Ok(stack),
+            None => Err(JsonResponse::<models::Stack>::build().not_found("not found")),
+        })?;
 
-    match stack {
-        Some(stack) => {
-            let id = stack.id.clone();
-            let dc = DcBuilder::new(stack);
-            let fc = dc.build();
-            tracing::debug!("Docker compose file content {:?}", fc);
+    let id = stack.id.clone();
+    let dc = DcBuilder::new(stack);
+    let fc = dc.build();
+    tracing::debug!("Docker compose file content {:?}", fc);
 
-            return Ok(JsonResponse::build()
-                .set_id(id)
-                .set_item(fc.unwrap())
-                .ok("Success")
-                );
-        }
-        None => {
-            return Err(JsonResponse::<Stack>::build().bad_request("Could not generate compose file"));
-        }
-    }
+    Ok(JsonResponse::build()
+        .set_id(id)
+        .set_item(fc.unwrap())
+        .ok("Success"))
 }
 
 #[tracing::instrument(name = "Generate docker-compose. Admin")]
 #[get("/{id}/compose")]
 pub async fn admin(
-    user: web::ReqData<Arc<User>>,
+    user: web::ReqData<Arc<models::User>>,
     path: web::Path<(i32,)>,
     pool: Data<PgPool>,
 ) -> Result<impl Responder> {
@@ -80,7 +49,7 @@ pub async fn admin(
     tracing::debug!("Received id: {}", id);
 
     let stack = match sqlx::query_as!(
-        Stack,
+        models::Stack,
         r#"
         SELECT * FROM user_stack WHERE id=$1 LIMIT 1
         "#,
@@ -108,23 +77,18 @@ pub async fn admin(
             let id = stack.id.clone();
             let dc = DcBuilder::new(stack);
             let fc = match dc.build() {
-                Some(fc) => {
-                    fc
-                }
+                Some(fc) => fc,
                 None => {
                     tracing::error!("Error. Compose builder returned an empty string");
                     "".to_string()
                 }
-
             };
             // tracing::debug!("Docker compose file content {:?}", fc);
-            return Ok(JsonResponse::build()
-                .set_id(id)
-                .set_item(fc).ok("Success"));
-
+            return Ok(JsonResponse::build().set_id(id).set_item(fc).ok("Success"));
         }
         None => {
-            return Err(JsonResponse::<Stack>::build().bad_request("Could not generate compose file"));
+            return Err(JsonResponse::<models::Stack>::build()
+                .bad_request("Could not generate compose file"));
         }
     }
 }

From 335b62ac3986a290ebbb511089ef5eab19597a02 Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 23 Dec 2023 18:35:42 +0200
Subject: [PATCH 095/284] issue-auth compose.rs admin route

---
 src/routes/stack/compose.rs | 59 +++++++++++--------------------------
 1 file changed, 17 insertions(+), 42 deletions(-)

diff --git a/src/routes/stack/compose.rs b/src/routes/stack/compose.rs
index 508d2341..d4e3eb4b 100644
--- a/src/routes/stack/compose.rs
+++ b/src/routes/stack/compose.rs
@@ -28,7 +28,7 @@ pub async fn add(
 
     let id = stack.id.clone();
     let dc = DcBuilder::new(stack);
-    let fc = dc.build();
+    let fc = dc.build(); //todo process the error
     tracing::debug!("Docker compose file content {:?}", fc);
 
     Ok(JsonResponse::build()
@@ -46,49 +46,24 @@ pub async fn admin(
 ) -> Result<impl Responder> {
     ///  Admin function for generating compose file for specified user
     let id = path.0;
-    tracing::debug!("Received id: {}", id);
+    let stack = db::stack::fetch(pool.get_ref(), id)
+        .await
+        .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(err))
+        .and_then(|stack| match stack {
+            Some(stack) => Ok(stack),
+            None => Err(JsonResponse::<models::Stack>::build().not_found("not found")),
+        })?;
 
-    let stack = match sqlx::query_as!(
-        models::Stack,
-        r#"
-        SELECT * FROM user_stack WHERE id=$1 LIMIT 1
-        "#,
-        id,
-    )
-    .fetch_one(pool.get_ref())
-    .await
-    {
-        Ok(stack) => {
-            tracing::info!("Record found: {:?}", stack.id);
-            Some(stack)
-        }
-        Err(sqlx::Error::RowNotFound) => {
-            tracing::error!("Record not found");
-            None
-        }
-        Err(e) => {
-            tracing::error!("Failed to fetch stack, error: {:?}", e);
-            None
+    let id = stack.id.clone();
+    let dc = DcBuilder::new(stack);
+    let fc = match dc.build() {
+        //todo process the error
+        Some(fc) => fc,
+        None => {
+            tracing::error!("Error. Compose builder returned an empty string");
+            "".to_string()
         }
     };
 
-    match stack {
-        Some(stack) => {
-            let id = stack.id.clone();
-            let dc = DcBuilder::new(stack);
-            let fc = match dc.build() {
-                Some(fc) => fc,
-                None => {
-                    tracing::error!("Error. Compose builder returned an empty string");
-                    "".to_string()
-                }
-            };
-            // tracing::debug!("Docker compose file content {:?}", fc);
-            return Ok(JsonResponse::build().set_id(id).set_item(fc).ok("Success"));
-        }
-        None => {
-            return Err(JsonResponse::<models::Stack>::build()
-                .bad_request("Could not generate compose file"));
-        }
-    }
+    Ok(JsonResponse::build().set_id(id).set_item(fc).ok("Success"))
 }

From 6d6bc2e8b051004d94403904bea35ecbb932cbce Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 23 Dec 2023 18:39:51 +0200
Subject: [PATCH 096/284] issue-auth compose logic. optimized

---
 src/routes/stack/compose.rs | 23 +++++++++--------------
 1 file changed, 9 insertions(+), 14 deletions(-)

diff --git a/src/routes/stack/compose.rs b/src/routes/stack/compose.rs
index d4e3eb4b..b92b0049 100644
--- a/src/routes/stack/compose.rs
+++ b/src/routes/stack/compose.rs
@@ -28,13 +28,12 @@ pub async fn add(
 
     let id = stack.id.clone();
     let dc = DcBuilder::new(stack);
-    let fc = dc.build(); //todo process the error
-    tracing::debug!("Docker compose file content {:?}", fc);
+    let fc = dc.build().ok_or_else(|| {
+        tracing::error!("Error. Compose builder returned an empty string");
+        JsonResponse::<models::Stack>::build().internal_server_error("troubles at building")
+    })?;
 
-    Ok(JsonResponse::build()
-        .set_id(id)
-        .set_item(fc.unwrap())
-        .ok("Success"))
+    Ok(JsonResponse::build().set_id(id).set_item(fc).ok("Success"))
 }
 
 #[tracing::instrument(name = "Generate docker-compose. Admin")]
@@ -56,14 +55,10 @@ pub async fn admin(
 
     let id = stack.id.clone();
     let dc = DcBuilder::new(stack);
-    let fc = match dc.build() {
-        //todo process the error
-        Some(fc) => fc,
-        None => {
-            tracing::error!("Error. Compose builder returned an empty string");
-            "".to_string()
-        }
-    };
+    let fc = dc.build().ok_or_else(|| {
+        tracing::error!("Error. Compose builder returned an empty string");
+        JsonResponse::<models::Stack>::build().internal_server_error("troubles at building")
+    })?;
 
     Ok(JsonResponse::build().set_id(id).set_item(fc).ok("Success"))
 }

From a77681cc7c152627d4fdd54a053563429fbc5bda Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 23 Dec 2023 18:45:00 +0200
Subject: [PATCH 097/284] issue-auth compose logic

---
 src/routes/stack/compose.rs | 9 +++------
 1 file changed, 3 insertions(+), 6 deletions(-)

diff --git a/src/routes/stack/compose.rs b/src/routes/stack/compose.rs
index b92b0049..28290902 100644
--- a/src/routes/stack/compose.rs
+++ b/src/routes/stack/compose.rs
@@ -1,9 +1,8 @@
-use actix_web::{get, web, web::Data, Responder, Result};
-
 use crate::db;
 use crate::helpers::stack::builder::DcBuilder;
 use crate::helpers::JsonResponse;
 use crate::models;
+use actix_web::{get, web, web::Data, Responder, Result};
 use sqlx::PgPool;
 use std::sync::Arc;
 
@@ -27,8 +26,7 @@ pub async fn add(
         })?;
 
     let id = stack.id.clone();
-    let dc = DcBuilder::new(stack);
-    let fc = dc.build().ok_or_else(|| {
+    let fc = DcBuilder::new(stack).build().ok_or_else(|| {
         tracing::error!("Error. Compose builder returned an empty string");
         JsonResponse::<models::Stack>::build().internal_server_error("troubles at building")
     })?;
@@ -54,8 +52,7 @@ pub async fn admin(
         })?;
 
     let id = stack.id.clone();
-    let dc = DcBuilder::new(stack);
-    let fc = dc.build().ok_or_else(|| {
+    let fc = DcBuilder::new(stack).build().ok_or_else(|| {
         tracing::error!("Error. Compose builder returned an empty string");
         JsonResponse::<models::Stack>::build().internal_server_error("troubles at building")
     })?;

From d584a685eb7fa79fcd8b2fb81c792a889f71392e Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 23 Dec 2023 18:52:42 +0200
Subject: [PATCH 098/284] issue-auth optimisation

---
 src/routes/stack/get.rs | 27 ++++++++++++++-------------
 1 file changed, 14 insertions(+), 13 deletions(-)

diff --git a/src/routes/stack/get.rs b/src/routes/stack/get.rs
index 18a044c5..ac21f342 100644
--- a/src/routes/stack/get.rs
+++ b/src/routes/stack/get.rs
@@ -1,31 +1,32 @@
+use crate::db;
 use crate::helpers::JsonResponse;
 use crate::models;
-use crate::db;
 use actix_web::{get, web, Responder, Result};
 use sqlx::PgPool;
 use std::convert::From;
-use tracing::Instrument;
 use std::sync::Arc;
+use tracing::Instrument;
 
 #[tracing::instrument(name = "Get logged user stack.")]
 #[get("/{id}")]
 pub async fn item(
     user: web::ReqData<Arc<models::User>>,
     path: web::Path<(i32,)>,
-    pool: web::Data<PgPool>, 
+    pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
     /// Get stack apps of logged user only
     let (id,) = path.into_inner();
 
-    let stack = db::stack::fetch(pool.get_ref(), id) 
+    let stack = db::stack::fetch(pool.get_ref(), id)
         .await
-        .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(err))?
-        .ok_or_else(|| JsonResponse::<models::Stack>::build().not_found("not found"))?
-        ;
-
-    if stack.user_id != user.id {
-        return Err(JsonResponse::<models::Stack>::build().bad_request("Forbidden"));
-    }
+        .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(err))
+        .and_then(|stack| match stack {
+            Some(stack) if stack.user_id != user.id => {
+                Err(JsonResponse::<models::Stack>::build().not_found("not found"))
+            }
+            Some(stack) => Ok(stack),
+            None => Err(JsonResponse::<models::Stack>::build().not_found("not found")),
+        })?;
 
     Ok(JsonResponse::build().set_item(Some(stack)).ok("OK"))
 }
@@ -42,8 +43,8 @@ pub async fn list(
     /// in order to pass validation at external deployment service
     let user_id = path.into_inner().0;
 
-    db::stack::fetch_by_user(pool.get_ref(), &user_id) 
+    db::stack::fetch_by_user(pool.get_ref(), &user_id)
         .await
-        .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(""))
+        .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(err))
         .map(|stacks| JsonResponse::build().set_list(stacks).ok("OK"))
 }

From 874162b8272890cbc378379cfd06fcefd6ff045a Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Tue, 26 Dec 2023 18:21:47 +0200
Subject: [PATCH 099/284] issue-auth stack/deploy

---
 src/routes/stack/deploy.rs | 117 +++++++++++++++----------------------
 1 file changed, 47 insertions(+), 70 deletions(-)

diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs
index 1d52caa7..3b0bc018 100644
--- a/src/routes/stack/deploy.rs
+++ b/src/routes/stack/deploy.rs
@@ -1,9 +1,9 @@
 use crate::configuration::Settings;
+use crate::db;
 use crate::forms::StackPayload;
 use crate::helpers::stack::builder::DcBuilder;
 use crate::helpers::JsonResponse;
-use crate::models::stack::Stack;
-use crate::models::user::User;
+use crate::models;
 use actix_web::{post, web, web::Data, Responder, Result};
 use futures_lite::stream::StreamExt;
 use lapin::{
@@ -12,89 +12,66 @@ use lapin::{
 use sqlx::PgPool;
 use std::sync::Arc;
 
-
 #[tracing::instrument(name = "Deploy for every user. Admin endpoint")]
 #[post("/{id}/deploy")]
 pub async fn add(
-    user: web::ReqData<Arc<User>>,
+    user: web::ReqData<Arc<models::User>>,
     path: web::Path<(i32,)>,
     pool: Data<PgPool>,
     sets: Data<Settings>,
 ) -> Result<impl Responder> {
     let id = path.0;
-    tracing::debug!("Received id: {}", id);
-
-    let stack = match sqlx::query_as!(
-        Stack,
-        r#"
-        SELECT * FROM user_stack WHERE id=$1 LIMIT 1
-        "#,
-        id
-    )
-    .fetch_one(pool.get_ref())
-    .await
-    {
-        Ok(stack) => {
-            tracing::info!("Stack found: {:?}", stack.id,);
-            Some(stack)
-        }
-        Err(sqlx::Error::RowNotFound) => {
-            tracing::error!("Row not found 404");
-            None
-        }
-        Err(e) => {
-            tracing::error!("Failed to fetch stack, error: {:?}", e);
-            None
-        }
-    };
+    let stack = db::stack::fetch(pool.get_ref(), id)
+        .await
+        .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(err))
+        .and_then(|stack| match stack {
+            Some(stack) => Ok(stack),
+            None => Err(JsonResponse::<models::Stack>::build().not_found("not found")),
+        })?;
 
-    return match stack {
-        Some(stack) => {
-            let id = stack.id.clone();
-            let dc = DcBuilder::new(stack);
-            dc.build();
+    let id = stack.id.clone();
+    let dc = DcBuilder::new(stack);
+    dc.build().ok_or_else(|| {
+        tracing::error!("Error. Compose builder returned an empty string");
+        JsonResponse::<models::Stack>::build().internal_server_error("troubles at building")
+    })?;
 
-            let addr = sets.amqp.connection_string();
-            let routing_key = "install.start.tfa.all.all".to_string();
-            tracing::debug!("Sending message to {:?}", routing_key);
+    let addr = sets.amqp.connection_string();
+    let routing_key = "install.start.tfa.all.all".to_string();
+    tracing::debug!("Sending message to {:?}", routing_key);
 
-            let conn = Connection::connect(&addr, ConnectionProperties::default())
-                .await
-                .expect("Could not connect RabbitMQ");
+    let conn = Connection::connect(&addr, ConnectionProperties::default())
+        .await
+        .expect("Could not connect RabbitMQ");
 
-            tracing::info!("RABBITMQ CONNECTED");
+    tracing::info!("RABBITMQ CONNECTED");
 
-            let channel = conn.create_channel().await.unwrap();
-            let mut stack_data =
-                serde_json::from_value::<StackPayload>(dc.stack.body.clone()).unwrap();
+    let channel = conn.create_channel().await.unwrap();
+    let mut stack_data = serde_json::from_value::<StackPayload>(dc.stack.body.clone()).unwrap();
 
-            stack_data.id = Some(id);
-            stack_data.user_token = Some(user.id.clone());
-            stack_data.user_email = Some(user.email.clone());
-            stack_data.stack_code = stack_data.custom.custom_stack_code.clone();
+    stack_data.id = Some(id);
+    stack_data.user_token = Some(user.id.clone());
+    stack_data.user_email = Some(user.email.clone());
+    stack_data.stack_code = stack_data.custom.custom_stack_code.clone();
 
-            let payload = serde_json::to_string::<StackPayload>(&stack_data).unwrap();
-            let _payload = payload.as_bytes();
+    let payload = serde_json::to_string::<StackPayload>(&stack_data).unwrap();
+    let _payload = payload.as_bytes();
 
-            let confirm = channel
-                .basic_publish(
-                    "install",
-                    routing_key.as_str(),
-                    BasicPublishOptions::default(),
-                    _payload,
-                    BasicProperties::default(),
-                )
-                .await
-                .unwrap()
-                .await
-                .unwrap();
+    let confirm = channel
+        .basic_publish(
+            "install",
+            routing_key.as_str(),
+            BasicPublishOptions::default(),
+            _payload,
+            BasicProperties::default(),
+        )
+        .await
+        .unwrap()
+        .await
+        .unwrap();
 
-            assert_eq!(confirm, Confirmation::NotRequested);
-            tracing::debug!("Message sent to rabbitmq");
-            return Ok(JsonResponse::<Stack>::build().set_id(id).ok("Success"));
-        }
-        None => {
-            Err(JsonResponse::<Stack>::build().internal_server_error("Deployment failed"))
-        }
-    }
+    assert_eq!(confirm, Confirmation::NotRequested);
+    return Ok(JsonResponse::<models::Stack>::build()
+        .set_id(id)
+        .ok("Success"));
 }

From 7ae0f1f13656cd9f5286f827d3cdef371f707754 Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Tue, 26 Dec 2023 18:36:32 +0200
Subject: [PATCH 100/284] issue-auth stack/deploy

---
 src/routes/stack/deploy.rs | 37 ++++++++++++++++++++++++++-----------
 1 file changed, 26 insertions(+), 11 deletions(-)

diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs
index 3b0bc018..928d6efe 100644
--- a/src/routes/stack/deploy.rs
+++ b/src/routes/stack/deploy.rs
@@ -42,13 +42,24 @@ pub async fn add(
 
     let conn = Connection::connect(&addr, ConnectionProperties::default())
         .await
-        .expect("Could not connect RabbitMQ");
-
-    tracing::info!("RABBITMQ CONNECTED");
+        .map_err(|err| {
+            JsonResponse::<models::Stack>::build()
+                .internal_server_error("Could not connect RabbitMQ")
+        })?;
 
-    let channel = conn.create_channel().await.unwrap();
-    let mut stack_data = serde_json::from_value::<StackPayload>(dc.stack.body.clone()).unwrap();
+    let channel = conn.create_channel().await.map_err(|err| {
+        JsonResponse::<models::Stack>::build()
+            .internal_server_error("Can't create rabbitMQ channel")
+    })?;
+    let mut stack_data = serde_json::from_value::<StackPayload>(dc.stack.body.clone())
+        .map_err(|err| JsonResponse::<models::Stack>::build().bad_request("can't deserialize"))?; //todo
+                                                                                                  //add
+                                                                                                  //json
+                                                                                                  //error
+                                                                                                  //path
 
+    Ok(JsonResponse::<models::Stack>::build().ok("bdc"))
+    /*
     stack_data.id = Some(id);
     stack_data.user_token = Some(user.id.clone());
     stack_data.user_email = Some(user.email.clone());
@@ -66,12 +77,16 @@ pub async fn add(
             BasicProperties::default(),
         )
         .await
-        .unwrap()
+        .unwrap() //todo
         .await
-        .unwrap();
-
-    assert_eq!(confirm, Confirmation::NotRequested);
-    return Ok(JsonResponse::<models::Stack>::build()
+        .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(err))
+        .and_then(|confirm| {
+            match confirm {
+                Confirmation::NotRequested => Err(JsonResponse::<models::Stack>::build().bad_request("confirmation is NotRequested")),
+                _ => Ok(JsonResponse::<models::Stack>::build()
         .set_id(id)
-        .ok("Success"));
+        .ok("Success"))
+            }
+        })
+    */
 }

From 36a88207c8ed97afc9ba2fb1e5d46a61129a4e5e Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Wed, 27 Dec 2023 17:29:47 +0200
Subject: [PATCH 101/284] issue-auth src/routes/stack/deploy.rs

---
 src/routes/stack/deploy.rs | 39 +++++++++++++++++++-------------------
 1 file changed, 19 insertions(+), 20 deletions(-)

diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs
index 928d6efe..03c52649 100644
--- a/src/routes/stack/deploy.rs
+++ b/src/routes/stack/deploy.rs
@@ -52,41 +52,40 @@ pub async fn add(
             .internal_server_error("Can't create rabbitMQ channel")
     })?;
     let mut stack_data = serde_json::from_value::<StackPayload>(dc.stack.body.clone())
-        .map_err(|err| JsonResponse::<models::Stack>::build().bad_request("can't deserialize"))?; //todo
-                                                                                                  //add
-                                                                                                  //json
-                                                                                                  //error
-                                                                                                  //path
+        .map_err(|err| JsonResponse::<models::Stack>::build().bad_request("can't deserialize"))?;
 
-    Ok(JsonResponse::<models::Stack>::build().ok("bdc"))
-    /*
     stack_data.id = Some(id);
     stack_data.user_token = Some(user.id.clone());
     stack_data.user_email = Some(user.email.clone());
     stack_data.stack_code = stack_data.custom.custom_stack_code.clone();
 
-    let payload = serde_json::to_string::<StackPayload>(&stack_data).unwrap();
-    let _payload = payload.as_bytes();
+    let payload = serde_json::to_string::<StackPayload>(&stack_data).map_err(|err| {
+        JsonResponse::<models::Stack>::build().internal_server_error(format!("{}", err))
+    })?;
 
-    let confirm = channel
+    channel
         .basic_publish(
             "install",
             routing_key.as_str(),
             BasicPublishOptions::default(),
-            _payload,
+            payload.as_bytes(),
             BasicProperties::default(),
         )
         .await
-        .unwrap() //todo
+        .map_err(|_| {
+            JsonResponse::<models::Stack>::build().internal_server_error("internal server error")
+        })? //todo the correct err
         .await
-        .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(err))
-        .and_then(|confirm| {
-            match confirm {
-                Confirmation::NotRequested => Err(JsonResponse::<models::Stack>::build().bad_request("confirmation is NotRequested")),
-                _ => Ok(JsonResponse::<models::Stack>::build()
-        .set_id(id)
-        .ok("Success"))
+        .map_err(|_| {
+            JsonResponse::<models::Stack>::build().internal_server_error("internal server error")
+        })
+        .and_then(|confirm| match confirm {
+            Confirmation::NotRequested => {
+                Err(JsonResponse::<models::Stack>::build()
+                    .bad_request("confirmation is NotRequested"))
             }
+            _ => Ok(JsonResponse::<models::Stack>::build()
+                .set_id(id)
+                .ok("Success")),
         })
-    */
 }

From d38f220031e279000f61c8543d09b0a3e6dc1909 Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Wed, 27 Dec 2023 17:41:24 +0200
Subject: [PATCH 102/284] issue-auth src/routes/stack/deploy.rs

---
 src/routes/stack/deploy.rs | 32 +++++++++++++++++++-------------
 1 file changed, 19 insertions(+), 13 deletions(-)

diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs
index 03c52649..36fbc281 100644
--- a/src/routes/stack/deploy.rs
+++ b/src/routes/stack/deploy.rs
@@ -33,7 +33,7 @@ pub async fn add(
     let dc = DcBuilder::new(stack);
     dc.build().ok_or_else(|| {
         tracing::error!("Error. Compose builder returned an empty string");
-        JsonResponse::<models::Stack>::build().internal_server_error("troubles at building")
+        JsonResponse::<models::Stack>::build().internal_server_error("")
     })?;
 
     let addr = sets.amqp.connection_string();
@@ -43,16 +43,19 @@ pub async fn add(
     let conn = Connection::connect(&addr, ConnectionProperties::default())
         .await
         .map_err(|err| {
-            JsonResponse::<models::Stack>::build()
-                .internal_server_error("Could not connect RabbitMQ")
+            tracing::error!("connecting to RabbitMQ {:?}", err);
+            JsonResponse::<models::Stack>::build().internal_server_error("")
         })?;
 
     let channel = conn.create_channel().await.map_err(|err| {
-        JsonResponse::<models::Stack>::build()
-            .internal_server_error("Can't create rabbitMQ channel")
+        tracing::error!("creating RabbitMQ channel {:?}", err);
+        JsonResponse::<models::Stack>::build().internal_server_error("")
     })?;
-    let mut stack_data = serde_json::from_value::<StackPayload>(dc.stack.body.clone())
-        .map_err(|err| JsonResponse::<models::Stack>::build().bad_request("can't deserialize"))?;
+    let mut stack_data =
+        serde_json::from_value::<StackPayload>(dc.stack.body.clone()).map_err(|err| {
+            tracing::error!("transforming json Value into StackPayload {:?}", err);
+            JsonResponse::<models::Stack>::build().bad_request("")
+        })?;
 
     stack_data.id = Some(id);
     stack_data.user_token = Some(user.id.clone());
@@ -60,7 +63,8 @@ pub async fn add(
     stack_data.stack_code = stack_data.custom.custom_stack_code.clone();
 
     let payload = serde_json::to_string::<StackPayload>(&stack_data).map_err(|err| {
-        JsonResponse::<models::Stack>::build().internal_server_error(format!("{}", err))
+        tracing::error!("serializing StackPayload {:?}", err);
+        JsonResponse::<models::Stack>::build().internal_server_error("")
     })?;
 
     channel
@@ -72,12 +76,14 @@ pub async fn add(
             BasicProperties::default(),
         )
         .await
-        .map_err(|_| {
-            JsonResponse::<models::Stack>::build().internal_server_error("internal server error")
-        })? //todo the correct err
+        .map_err(|err| {
+            tracing::error!("publishing the message {:?}", err);
+            JsonResponse::<models::Stack>::build().internal_server_error("")
+        })?
         .await
-        .map_err(|_| {
-            JsonResponse::<models::Stack>::build().internal_server_error("internal server error")
+        .map_err(|err| {
+            tracing::error!("confirming the publication {:?}", err);
+            JsonResponse::<models::Stack>::build().internal_server_error("")
         })
         .and_then(|confirm| match confirm {
             Confirmation::NotRequested => {

From 1852cb623a5d7222a4f269e31cec7544cdfb65cd Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Wed, 27 Dec 2023 17:47:36 +0200
Subject: [PATCH 103/284] issue-auth channel

---
 src/routes/stack/deploy.rs | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs
index 36fbc281..9432f552 100644
--- a/src/routes/stack/deploy.rs
+++ b/src/routes/stack/deploy.rs
@@ -47,10 +47,6 @@ pub async fn add(
             JsonResponse::<models::Stack>::build().internal_server_error("")
         })?;
 
-    let channel = conn.create_channel().await.map_err(|err| {
-        tracing::error!("creating RabbitMQ channel {:?}", err);
-        JsonResponse::<models::Stack>::build().internal_server_error("")
-    })?;
     let mut stack_data =
         serde_json::from_value::<StackPayload>(dc.stack.body.clone()).map_err(|err| {
             tracing::error!("transforming json Value into StackPayload {:?}", err);
@@ -67,7 +63,12 @@ pub async fn add(
         JsonResponse::<models::Stack>::build().internal_server_error("")
     })?;
 
-    channel
+    conn.create_channel()
+        .await
+        .map_err(|err| {
+            tracing::error!("creating RabbitMQ channel {:?}", err);
+            JsonResponse::<models::Stack>::build().internal_server_error("")
+        })?
         .basic_publish(
             "install",
             routing_key.as_str(),

From 3383358c5afcf024f7e1217adf1113ef7864a9fe Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Wed, 27 Dec 2023 17:51:17 +0200
Subject: [PATCH 104/284] issue-auth RabbitMQ logic

---
 src/routes/stack/deploy.rs | 23 +++++++++++------------
 1 file changed, 11 insertions(+), 12 deletions(-)

diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs
index 9432f552..c92aa70a 100644
--- a/src/routes/stack/deploy.rs
+++ b/src/routes/stack/deploy.rs
@@ -36,17 +36,6 @@ pub async fn add(
         JsonResponse::<models::Stack>::build().internal_server_error("")
     })?;
 
-    let addr = sets.amqp.connection_string();
-    let routing_key = "install.start.tfa.all.all".to_string();
-    tracing::debug!("Sending message to {:?}", routing_key);
-
-    let conn = Connection::connect(&addr, ConnectionProperties::default())
-        .await
-        .map_err(|err| {
-            tracing::error!("connecting to RabbitMQ {:?}", err);
-            JsonResponse::<models::Stack>::build().internal_server_error("")
-        })?;
-
     let mut stack_data =
         serde_json::from_value::<StackPayload>(dc.stack.body.clone()).map_err(|err| {
             tracing::error!("transforming json Value into StackPayload {:?}", err);
@@ -63,7 +52,17 @@ pub async fn add(
         JsonResponse::<models::Stack>::build().internal_server_error("")
     })?;
 
-    conn.create_channel()
+    let addr = sets.amqp.connection_string();
+    let routing_key = "install.start.tfa.all.all".to_string();
+    tracing::debug!("Sending message to {:?}", routing_key);
+
+    Connection::connect(&addr, ConnectionProperties::default())
+        .await
+        .map_err(|err| {
+            tracing::error!("connecting to RabbitMQ {:?}", err);
+            JsonResponse::<models::Stack>::build().internal_server_error("")
+        })?
+        .create_channel()
         .await
         .map_err(|err| {
             tracing::error!("creating RabbitMQ channel {:?}", err);

From 055988c936b9c76d127751461f0f4fdb2a2c0dbf Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 30 Dec 2023 09:33:15 +0200
Subject: [PATCH 105/284] issue-auth pg_pool

---
 src/routes/client/add.rs     | 22 +++++++++++-----------
 src/routes/client/disable.rs | 32 ++++++++++++++++----------------
 src/routes/client/enable.rs  | 15 +++++++--------
 src/routes/client/update.rs  | 20 ++++++++++++--------
 src/routes/rating/get.rs     | 13 ++++++++-----
 src/routes/stack/add.rs      | 10 +++++-----
 src/routes/stack/compose.rs  |  8 ++++----
 src/routes/stack/deploy.rs   |  4 ++--
 src/routes/stack/get.rs      |  8 ++++----
 src/routes/stack/update.rs   | 34 +++++++++++++---------------------
 10 files changed, 82 insertions(+), 84 deletions(-)

diff --git a/src/routes/client/add.rs b/src/routes/client/add.rs
index b3d90d94..0b526d10 100644
--- a/src/routes/client/add.rs
+++ b/src/routes/client/add.rs
@@ -1,21 +1,21 @@
 use crate::configuration::Settings;
+use crate::db;
 use crate::helpers::client;
 use crate::helpers::JsonResponse;
 use crate::models;
-use crate::db;
 use actix_web::{post, web, Responder, Result};
 use sqlx::PgPool;
-use tracing::Instrument;
 use std::sync::Arc;
+use tracing::Instrument;
 
 #[tracing::instrument(name = "Add client.")]
 #[post("")]
 pub async fn add_handler(
     user: web::ReqData<Arc<models::User>>,
     settings: web::Data<Settings>,
-    pool: web::Data<PgPool>,
+    pg_pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
-    add_handler_inner(&user.id, settings, pool)
+    add_handler_inner(&user.id, settings, pg_pool)
         .await
         .map(|client| JsonResponse::build().set_item(client).ok("Ok"))
         .map_err(|err| JsonResponse::<models::Client>::build().bad_request(err))
@@ -24,21 +24,21 @@ pub async fn add_handler(
 pub async fn add_handler_inner(
     user_id: &String,
     settings: web::Data<Settings>,
-    pool: web::Data<PgPool>,
+    pg_pool: web::Data<PgPool>,
 ) -> Result<models::Client, String> {
-    let client_count = db::client::count_by_user(pool.get_ref(), user_id).await?;
+    let client_count = db::client::count_by_user(pg_pool.get_ref(), user_id).await?;
     if client_count >= settings.max_clients_number {
         return Err("Too many clients created".to_string());
     }
 
-    let client = create_client(pool.get_ref(), user_id).await?;
-    db::client::insert(pool.get_ref(), client).await
+    let client = create_client(pg_pool.get_ref(), user_id).await?;
+    db::client::insert(pg_pool.get_ref(), client).await
 }
 
-async fn create_client(pool: &PgPool, user_id: &String) -> Result<models::Client, String> {
-    let mut client = models::Client::default(); 
+async fn create_client(pg_pool: &PgPool, user_id: &String) -> Result<models::Client, String> {
+    let mut client = models::Client::default();
     client.user_id = user_id.clone();
-    client.secret = client::generate_secret(pool, 255)
+    client.secret = client::generate_secret(pg_pool, 255)
         .await
         .map(|s| Some(s))?;
 
diff --git a/src/routes/client/disable.rs b/src/routes/client/disable.rs
index 057a183f..70d2a1cb 100644
--- a/src/routes/client/disable.rs
+++ b/src/routes/client/disable.rs
@@ -1,33 +1,33 @@
 use crate::configuration::Settings;
+use crate::db;
 use crate::helpers::JsonResponse;
 use crate::models;
-use crate::db;
 use actix_web::{put, web, Responder, Result};
 use sqlx::PgPool;
-use tracing::Instrument;
 use std::sync::Arc;
+use tracing::Instrument;
 
 #[tracing::instrument(name = "Disable client.")]
 #[put("/{id}/disable")]
 pub async fn disable_handler(
     user: web::ReqData<Arc<models::User>>,
     settings: web::Data<Settings>,
-    pool: web::Data<PgPool>,
+    pg_pool: web::Data<PgPool>,
     path: web::Path<(i32,)>,
 ) -> Result<impl Responder> {
-        let client_id = path.0;
-        let mut client = db::client::fetch(pool.get_ref(), client_id)
-            .await
-            .map_err(|msg| JsonResponse::<models::Client>::build().internal_server_error(msg))?
-            .ok_or_else(|| JsonResponse::<models::Client>::build().not_found("not found"))?; 
+    let client_id = path.0;
+    let mut client = db::client::fetch(pg_pool.get_ref(), client_id)
+        .await
+        .map_err(|msg| JsonResponse::<models::Client>::build().internal_server_error(msg))?
+        .ok_or_else(|| JsonResponse::<models::Client>::build().not_found("not found"))?;
 
-        if client.secret.is_none() {
-            return Err(JsonResponse::<models::Client>::build().bad_request("client is not active"));
-        }
+    if client.secret.is_none() {
+        return Err(JsonResponse::<models::Client>::build().bad_request("client is not active"));
+    }
 
-        client.secret = None;
-        db::client::update(pool.get_ref(), client)
-            .await
-            .map(|client| JsonResponse::build().set_item(client).ok("success"))
-            .map_err(|msg| JsonResponse::<models::Client>::build().bad_request(msg))
+    client.secret = None;
+    db::client::update(pg_pool.get_ref(), client)
+        .await
+        .map(|client| JsonResponse::build().set_item(client).ok("success"))
+        .map_err(|msg| JsonResponse::<models::Client>::build().bad_request(msg))
 }
diff --git a/src/routes/client/enable.rs b/src/routes/client/enable.rs
index 5669accc..c87fc445 100644
--- a/src/routes/client/enable.rs
+++ b/src/routes/client/enable.rs
@@ -1,38 +1,37 @@
 use crate::configuration::Settings;
+use crate::db;
 use crate::helpers::client;
 use crate::helpers::JsonResponse;
 use crate::models;
-use crate::db;
 use actix_web::{put, web, Responder, Result};
-use tracing::Instrument;
 use sqlx::PgPool;
 use std::sync::Arc;
+use tracing::Instrument;
 
 #[tracing::instrument(name = "Enable client.")]
 #[put("/{id}/enable")]
 pub async fn enable_handler(
     user: web::ReqData<Arc<models::User>>,
     settings: web::Data<Settings>,
-    pool: web::Data<PgPool>,
+    pg_pool: web::Data<PgPool>,
     path: web::Path<(i32,)>,
 ) -> Result<impl Responder> {
     let client_id = path.0;
-    let mut client = db::client::fetch(pool.get_ref(), client_id)
+    let mut client = db::client::fetch(pg_pool.get_ref(), client_id)
         .await
         .map_err(|msg| JsonResponse::<models::Client>::build().internal_server_error(msg))?
-        .ok_or_else(|| JsonResponse::<models::Client>::build().not_found("not found"))?
-        ; 
+        .ok_or_else(|| JsonResponse::<models::Client>::build().not_found("not found"))?;
 
     if client.secret.is_some() {
         return Err(JsonResponse::<models::Client>::build().bad_request("client is already active"));
     }
 
-    client.secret = client::generate_secret(pool.get_ref(), 255)
+    client.secret = client::generate_secret(pg_pool.get_ref(), 255)
         .await
         .map(|secret| Some(secret))
         .map_err(|err| JsonResponse::<models::Client>::build().bad_request(err))?;
 
-    db::client::update(pool.get_ref(), client)
+    db::client::update(pg_pool.get_ref(), client)
         .await
         .map(|client| JsonResponse::build().set_item(client).ok("success"))
         .map_err(|err| JsonResponse::<models::Client>::build().bad_request(err))
diff --git a/src/routes/client/update.rs b/src/routes/client/update.rs
index b1003162..a52af9b0 100644
--- a/src/routes/client/update.rs
+++ b/src/routes/client/update.rs
@@ -1,39 +1,43 @@
+use crate::db;
 use crate::helpers::client;
 use crate::models;
-use crate::db;
 use crate::{configuration::Settings, helpers::JsonResponse};
 use actix_web::{put, web, Responder, Result};
+use futures::TryFutureExt;
 use sqlx::PgPool;
 use std::sync::Arc;
 use tracing::Instrument;
-use futures::TryFutureExt;
 
 #[tracing::instrument(name = "Update client.")]
 #[put("/{id}")]
 pub async fn update_handler(
     user: web::ReqData<Arc<models::User>>,
     settings: web::Data<Settings>,
-    pool: web::Data<PgPool>,
+    pg_pool: web::Data<PgPool>,
     path: web::Path<(i32,)>,
 ) -> Result<impl Responder> {
     let client_id = path.0;
-    let mut client = db::client::fetch(pool.get_ref(), client_id)
+    let mut client = db::client::fetch(pg_pool.get_ref(), client_id)
         .await
         .map_err(|msg| JsonResponse::<models::Client>::build().internal_server_error(msg))?
-        .ok_or_else(|| JsonResponse::<models::Client>::build().not_found("not found"))?; 
+        .ok_or_else(|| JsonResponse::<models::Client>::build().not_found("not found"))?;
 
     if client.secret.is_none() {
         return Err(JsonResponse::<models::Client>::build().bad_request("client is not active"));
     }
 
-    client.secret = client::generate_secret(pool.get_ref(), 255)
+    client.secret = client::generate_secret(pg_pool.get_ref(), 255)
         .await
         .map(|s| Some(s))
         .map_err(|msg| JsonResponse::<models::Client>::build().bad_request(msg))?;
 
-    db::client::update(pool.get_ref(), client)
+    db::client::update(pg_pool.get_ref(), client)
         .await
-        .map(|client| JsonResponse::<models::Client>::build().set_item(client).ok("success"))
+        .map(|client| {
+            JsonResponse::<models::Client>::build()
+                .set_item(client)
+                .ok("success")
+        })
         .map_err(|err| {
             tracing::error!("Failed to execute query: {:?}", err);
             JsonResponse::<models::Client>::build().internal_server_error("")
diff --git a/src/routes/rating/get.rs b/src/routes/rating/get.rs
index 4bc0c1ff..9081960c 100644
--- a/src/routes/rating/get.rs
+++ b/src/routes/rating/get.rs
@@ -1,6 +1,6 @@
+use crate::db;
 use crate::helpers::JsonResponse;
 use crate::models;
-use crate::db;
 use actix_web::{get, web, Responder, Result};
 use sqlx::PgPool;
 use tracing::Instrument;
@@ -14,10 +14,10 @@ use tracing::Instrument;
 #[get("/{id}")]
 pub async fn get_handler(
     path: web::Path<(i32,)>,
-    pool: web::Data<PgPool>,
+    pg_pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
     let rate_id = path.0;
-    let rating = db::rating::fetch(pool.get_ref(), rate_id)
+    let rating = db::rating::fetch(pg_pool.get_ref(), rate_id)
         .await
         .map_err(|_err| JsonResponse::<models::Rating>::build().internal_server_error(""))?
         .ok_or_else(|| JsonResponse::<models::Rating>::build().not_found("not found"))?;
@@ -27,8 +27,11 @@ pub async fn get_handler(
 
 #[tracing::instrument(name = "Get all ratings.")]
 #[get("")]
-pub async fn list_handler(path: web::Path<()>, pool: web::Data<PgPool>) -> Result<impl Responder> {
-    db::rating::fetch_all(pool.get_ref())
+pub async fn list_handler(
+    path: web::Path<()>,
+    pg_pool: web::Data<PgPool>,
+) -> Result<impl Responder> {
+    db::rating::fetch_all(pg_pool.get_ref())
         .await
         .map(|ratings| JsonResponse::build().set_list(ratings).ok("OK"))
         .map_err(|err| JsonResponse::<models::Rating>::build().internal_server_error(""))
diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs
index ef38ac5f..7ed5a09b 100644
--- a/src/routes/stack/add.rs
+++ b/src/routes/stack/add.rs
@@ -19,20 +19,20 @@ use std::sync::Arc;
 pub async fn add(
     body: Bytes,
     user: web::ReqData<Arc<models::User>>,
-    pool: Data<PgPool>,
+    pg_pool: Data<PgPool>,
 ) -> Result<impl Responder> {
     // @todo ACL
     let form = body_into_form(body).await?;
     let stack_name = form.custom.custom_stack_code.clone();
 
-    check_if_stack_exists(pool.get_ref(), &stack_name).await?;
+    check_if_stack_exists(pg_pool.get_ref(), &stack_name).await?;
 
     let body: Value = serde_json::to_value::<StackForm>(form)
         .or(serde_json::to_value::<StackForm>(StackForm::default()))
         .unwrap();
 
     let stack = models::Stack::new(user.id.clone(), stack_name, body);
-    db::stack::insert(pool.get_ref(), stack)
+    db::stack::insert(pg_pool.get_ref(), stack)
         .await
         .map(|stack| JsonResponse::build().set_item(stack).ok("Ok"))
         .map_err(|_| {
@@ -40,8 +40,8 @@ pub async fn add(
         })
 }
 
-async fn check_if_stack_exists(pool: &PgPool, stack_name: &String) -> Result<(), Error> {
-    db::stack::fetch_one_by_name(pool, stack_name)
+async fn check_if_stack_exists(pg_pool: &PgPool, stack_name: &String) -> Result<(), Error> {
+    db::stack::fetch_one_by_name(pg_pool, stack_name)
         .await
         .map_err(|_| {
             JsonResponse::<models::Stack>::build().internal_server_error("Internal Server Error")
diff --git a/src/routes/stack/compose.rs b/src/routes/stack/compose.rs
index 28290902..4e58141e 100644
--- a/src/routes/stack/compose.rs
+++ b/src/routes/stack/compose.rs
@@ -11,10 +11,10 @@ use std::sync::Arc;
 pub async fn add(
     user: web::ReqData<Arc<models::User>>,
     path: web::Path<(i32,)>,
-    pool: Data<PgPool>,
+    pg_pool: Data<PgPool>,
 ) -> Result<impl Responder> {
     let id = path.0;
-    let stack = db::stack::fetch(pool.get_ref(), id)
+    let stack = db::stack::fetch(pg_pool.get_ref(), id)
         .await
         .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(err))
         .and_then(|stack| match stack {
@@ -39,11 +39,11 @@ pub async fn add(
 pub async fn admin(
     user: web::ReqData<Arc<models::User>>,
     path: web::Path<(i32,)>,
-    pool: Data<PgPool>,
+    pg_pool: Data<PgPool>,
 ) -> Result<impl Responder> {
     ///  Admin function for generating compose file for specified user
     let id = path.0;
-    let stack = db::stack::fetch(pool.get_ref(), id)
+    let stack = db::stack::fetch(pg_pool.get_ref(), id)
         .await
         .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(err))
         .and_then(|stack| match stack {
diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs
index c92aa70a..f18c99d6 100644
--- a/src/routes/stack/deploy.rs
+++ b/src/routes/stack/deploy.rs
@@ -17,11 +17,11 @@ use std::sync::Arc;
 pub async fn add(
     user: web::ReqData<Arc<models::User>>,
     path: web::Path<(i32,)>,
-    pool: Data<PgPool>,
+    pg_pool: Data<PgPool>,
     sets: Data<Settings>,
 ) -> Result<impl Responder> {
     let id = path.0;
-    let stack = db::stack::fetch(pool.get_ref(), id)
+    let stack = db::stack::fetch(pg_pool.get_ref(), id)
         .await
         .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(err))
         .and_then(|stack| match stack {
diff --git a/src/routes/stack/get.rs b/src/routes/stack/get.rs
index ac21f342..1e9492a5 100644
--- a/src/routes/stack/get.rs
+++ b/src/routes/stack/get.rs
@@ -12,12 +12,12 @@ use tracing::Instrument;
 pub async fn item(
     user: web::ReqData<Arc<models::User>>,
     path: web::Path<(i32,)>,
-    pool: web::Data<PgPool>,
+    pg_pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
     /// Get stack apps of logged user only
     let (id,) = path.into_inner();
 
-    let stack = db::stack::fetch(pool.get_ref(), id)
+    let stack = db::stack::fetch(pg_pool.get_ref(), id)
         .await
         .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(err))
         .and_then(|stack| match stack {
@@ -36,14 +36,14 @@ pub async fn item(
 pub async fn list(
     user: web::ReqData<Arc<models::User>>,
     path: web::Path<(String,)>,
-    pool: web::Data<PgPool>,
+    pg_pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
     /// This is admin endpoint, used by a m2m app, client app is confidential
     /// it should return stacks by user id
     /// in order to pass validation at external deployment service
     let user_id = path.into_inner().0;
 
-    db::stack::fetch_by_user(pool.get_ref(), &user_id)
+    db::stack::fetch_by_user(pg_pool.get_ref(), &user_id)
         .await
         .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(err))
         .map(|stacks| JsonResponse::build().set_list(stacks).ok("OK"))
diff --git a/src/routes/stack/update.rs b/src/routes/stack/update.rs
index a418b532..229553f7 100644
--- a/src/routes/stack/update.rs
+++ b/src/routes/stack/update.rs
@@ -1,21 +1,16 @@
-use chrono::Utc;
-use actix_web::{
-    web,
-    web::{Data},
-    Responder, Result,
-};
 use crate::forms::stack::StackForm;
 use crate::helpers::JsonResponse;
+use crate::models;
 use crate::models::user::User;
 use actix_web::post;
+use actix_web::{web, web::Data, Responder, Result};
+use chrono::Utc;
 use serde_json::Value;
-use sqlx::PgPool;
 use serde_valid::Validate;
+use sqlx::PgPool;
+use std::sync::Arc;
 use tracing::Instrument;
 use uuid::Uuid;
-use crate::models;
-use std::sync::Arc;
-
 
 #[tracing::instrument(name = "Update stack.")]
 #[post("/{id}")]
@@ -23,18 +18,14 @@ pub async fn update(
     path: web::Path<(i32,)>,
     form: web::Json<StackForm>,
     user: web::ReqData<Arc<User>>,
-    pool: Data<PgPool>,
+    pg_pool: Data<PgPool>,
 ) -> Result<impl Responder> {
     // @todo ACL
 
     let (id,) = path.into_inner();
     let query_span = tracing::info_span!("Check existence by id.");
-    match sqlx::query_as!(
-        models::Stack,
-        r"SELECT * FROM user_stack WHERE id = $1",
-        id
-    )
-        .fetch_one(pool.get_ref())
+    match sqlx::query_as!(models::Stack, r"SELECT * FROM user_stack WHERE id = $1", id)
+        .fetch_one(pg_pool.get_ref())
         .instrument(query_span)
         .await
     {
@@ -60,7 +51,8 @@ pub async fn update(
     );
     let _request_span_guard = request_span.enter(); // ->exit
 
-    tracing::info!("request_id {} Updating '{}' '{}'",
+    tracing::info!(
+        "request_id {} Updating '{}' '{}'",
         request_id,
         form.custom.project_name,
         form.region
@@ -97,9 +89,9 @@ pub async fn update(
         Utc::now(),
         Utc::now(),
     )
-        .execute(pool.get_ref())
-        .instrument(query_span)
-        .await
+    .execute(pg_pool.get_ref())
+    .instrument(query_span)
+    .await
     {
         Ok(record) => {
             tracing::info!(

From ace2c26cfb382913459864d2f02f5a9133c86935 Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 30 Dec 2023 09:39:20 +0200
Subject: [PATCH 106/284] issue-auth db_pool renamed to pg_pool

---
 src/startup.rs | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/src/startup.rs b/src/startup.rs
index 8b966eef..b592a4f3 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -12,17 +12,16 @@ use tracing_actix_web::TracingLogger;
 
 pub async fn run(
     listener: TcpListener,
-    db_pool: Pool<Postgres>,
+    pg_pool: Pool<Postgres>,
     settings: Settings,
 ) -> Result<Server, std::io::Error> {
     let settings = web::Data::new(settings);
-    let db_pool = web::Data::new(db_pool);
+    let pg_pool = web::Data::new(pg_pool);
 
     let server = HttpServer::new(move || {
         App::new()
             .wrap(TracingLogger::default())
-            .service(web::scope("/health_check")
-                .service(crate::routes::health_check))
+            .service(web::scope("/health_check").service(crate::routes::health_check))
             .service(
                 web::scope("/client")
                     .wrap(HttpAuthentication::bearer(
@@ -64,7 +63,7 @@ pub async fn run(
                     .service(crate::routes::stack::add::add)
                     .service(crate::routes::stack::update::update),
             )
-            .app_data(db_pool.clone())
+            .app_data(pg_pool.clone())
             .app_data(settings.clone())
     })
     .listen(listener)?

From 1d4fc9688f5b0c715b752f660075fa68c056ecb3 Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 30 Dec 2023 10:21:13 +0200
Subject: [PATCH 107/284] issue-auth helpers::MqPool

---
 Cargo.lock                 | 40 +++++++++++++++++++++++++++++++++++++-
 Cargo.toml                 |  1 +
 src/helpers/mod.rs         |  2 ++
 src/helpers/mq_pool.rs     | 27 +++++++++++++++++++++++++
 src/main.rs                |  4 ++--
 src/routes/stack/deploy.rs |  3 ++-
 src/startup.rs             |  5 +++++
 7 files changed, 78 insertions(+), 4 deletions(-)
 create mode 100644 src/helpers/mq_pool.rs

diff --git a/Cargo.lock b/Cargo.lock
index 73e0b28a..e7d155c4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -975,11 +975,37 @@ dependencies = [
  "tokio",
 ]
 
+[[package]]
+name = "deadpool"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb84100978c1c7b37f09ed3ce3e5f843af02c2a2c431bae5b19230dad2c1b490"
+dependencies = [
+ "async-trait",
+ "deadpool-runtime",
+ "num_cpus",
+ "tokio",
+]
+
+[[package]]
+name = "deadpool-lapin"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce11c0dc86703e59a8921bb9afee10b13c242e47624347bd3a3b545c41db556e"
+dependencies = [
+ "deadpool 0.10.0",
+ "lapin",
+ "tokio-executor-trait",
+]
+
 [[package]]
 name = "deadpool-runtime"
 version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "63dfa964fe2a66f3fde91fc70b267fe193d822c7e603e2a675a49a7f46ad3f49"
+dependencies = [
+ "tokio",
+]
 
 [[package]]
 name = "deranged"
@@ -3103,6 +3129,7 @@ dependencies = [
  "chrono",
  "clap",
  "config",
+ "deadpool-lapin",
  "derive_builder",
  "futures",
  "futures-lite 1.13.0",
@@ -3329,6 +3356,17 @@ dependencies = [
  "windows-sys 0.48.0",
 ]
 
+[[package]]
+name = "tokio-executor-trait"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "802ccf58e108fe16561f35348fabe15ff38218968f033d587e399a84937533cc"
+dependencies = [
+ "async-trait",
+ "executor-trait",
+ "tokio",
+]
+
 [[package]]
 name = "tokio-macros"
 version = "2.2.0"
@@ -3940,7 +3978,7 @@ dependencies = [
  "assert-json-diff",
  "async-trait",
  "base64 0.21.5",
- "deadpool",
+ "deadpool 0.9.5",
  "futures",
  "futures-timer",
  "http-types",
diff --git a/Cargo.toml b/Cargo.toml
index 54ee40f6..4555de3b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -53,6 +53,7 @@ lapin = { version = "2.3.1", features = ["serde_json"] }
 futures-lite = "1.13.0"
 clap = { version = "4.4.8", features = ["derive"] }
 serde_path_to_error = "0.1.14"
+deadpool-lapin = "0.11.0"
 
 [dependencies.sqlx]
 version = "0.6.3"
diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs
index e5439f74..6d7d2a16 100644
--- a/src/helpers/mod.rs
+++ b/src/helpers/mod.rs
@@ -1,5 +1,7 @@
 pub mod client;
 pub(crate) mod json;
+mod mq_pool;
 pub(crate) mod stack;
 
 pub use json::*;
+pub use mq_pool::MqPool;
diff --git a/src/helpers/mq_pool.rs b/src/helpers/mq_pool.rs
new file mode 100644
index 00000000..4e5bcbd1
--- /dev/null
+++ b/src/helpers/mq_pool.rs
@@ -0,0 +1,27 @@
+use deadpool_lapin::{Config, CreatePoolError, Pool, Runtime};
+
+#[derive(Debug)]
+pub struct MqPool {
+    pool: Pool,
+}
+
+impl MqPool {
+    pub fn try_new(url: String) -> Result<Self, std::io::Error> {
+        let mut cfg = Config::default();
+        cfg.url = Some(url);
+        let pool = cfg.create_pool(Some(Runtime::Tokio1)).map_err(|err| {
+            tracing::error!("{:?}", err);
+
+            match err {
+                CreatePoolError::Config(_) => {
+                    std::io::Error::new(std::io::ErrorKind::Other, "config error")
+                }
+                CreatePoolError::Build(_) => {
+                    std::io::Error::new(std::io::ErrorKind::Other, "build error")
+                }
+            }
+        })?;
+
+        Ok(MqPool { pool })
+    }
+}
diff --git a/src/main.rs b/src/main.rs
index ebf4987c..8132f582 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -11,7 +11,7 @@ async fn main() -> std::io::Result<()> {
 
     let settings = get_configuration().expect("Failed to read configuration.");
 
-    let db_pool = PgPool::connect(&settings.database.connection_string())
+    let pg_pool = PgPool::connect(&settings.database.connection_string())
         .await
         .expect("Failed to connect to database.");
 
@@ -20,5 +20,5 @@ async fn main() -> std::io::Result<()> {
     let listener =
         TcpListener::bind(address).expect(&format!("failed to bind to {}", settings.app_port));
 
-    run(listener, db_pool, settings).await?.await
+    run(listener, pg_pool, settings).await?.await
 }
diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs
index f18c99d6..2593c46e 100644
--- a/src/routes/stack/deploy.rs
+++ b/src/routes/stack/deploy.rs
@@ -2,7 +2,7 @@ use crate::configuration::Settings;
 use crate::db;
 use crate::forms::StackPayload;
 use crate::helpers::stack::builder::DcBuilder;
-use crate::helpers::JsonResponse;
+use crate::helpers::{JsonResponse, MqPool};
 use crate::models;
 use actix_web::{post, web, web::Data, Responder, Result};
 use futures_lite::stream::StreamExt;
@@ -18,6 +18,7 @@ pub async fn add(
     user: web::ReqData<Arc<models::User>>,
     path: web::Path<(i32,)>,
     pg_pool: Data<PgPool>,
+    mq_pool: Data<MqPool>,
     sets: Data<Settings>,
 ) -> Result<impl Responder> {
     let id = path.0;
diff --git a/src/startup.rs b/src/startup.rs
index b592a4f3..88312023 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -1,4 +1,5 @@
 use crate::configuration::Settings;
+use crate::helpers;
 use actix_cors::Cors;
 use actix_web::dev::Server;
 use actix_web::{
@@ -18,6 +19,9 @@ pub async fn run(
     let settings = web::Data::new(settings);
     let pg_pool = web::Data::new(pg_pool);
 
+    let mq_pool = helpers::MqPool::try_new(settings.amqp.connection_string())?;
+    let mq_pool = web::Data::new(mq_pool);
+
     let server = HttpServer::new(move || {
         App::new()
             .wrap(TracingLogger::default())
@@ -64,6 +68,7 @@ pub async fn run(
                     .service(crate::routes::stack::update::update),
             )
             .app_data(pg_pool.clone())
+            .app_data(mq_pool.clone())
             .app_data(settings.clone())
     })
     .listen(listener)?

From c6bf4a2c222ab43d2550c85e6d34e7d6f192b403 Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 30 Dec 2023 10:31:31 +0200
Subject: [PATCH 108/284] issue-auth mq_manager

---
 src/helpers/mod.rs                        | 4 ++--
 src/helpers/{mq_pool.rs => mq_manager.rs} | 8 +++++---
 src/routes/stack/deploy.rs                | 4 ++--
 src/startup.rs                            | 6 +++---
 4 files changed, 12 insertions(+), 10 deletions(-)
 rename src/helpers/{mq_pool.rs => mq_manager.rs} (87%)

diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs
index 6d7d2a16..6d3b1699 100644
--- a/src/helpers/mod.rs
+++ b/src/helpers/mod.rs
@@ -1,7 +1,7 @@
 pub mod client;
 pub(crate) mod json;
-mod mq_pool;
+mod mq_manager;
 pub(crate) mod stack;
 
 pub use json::*;
-pub use mq_pool::MqPool;
+pub use mq_manager::MqManager;
diff --git a/src/helpers/mq_pool.rs b/src/helpers/mq_manager.rs
similarity index 87%
rename from src/helpers/mq_pool.rs
rename to src/helpers/mq_manager.rs
index 4e5bcbd1..00b95261 100644
--- a/src/helpers/mq_pool.rs
+++ b/src/helpers/mq_manager.rs
@@ -1,11 +1,11 @@
 use deadpool_lapin::{Config, CreatePoolError, Pool, Runtime};
 
 #[derive(Debug)]
-pub struct MqPool {
+pub struct MqManager {
     pool: Pool,
 }
 
-impl MqPool {
+impl MqManager {
     pub fn try_new(url: String) -> Result<Self, std::io::Error> {
         let mut cfg = Config::default();
         cfg.url = Some(url);
@@ -22,6 +22,8 @@ impl MqPool {
             }
         })?;
 
-        Ok(MqPool { pool })
+        Ok(Self { pool })
     }
+
+    pub async fn create_channel() {}
 }
diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs
index 2593c46e..dc00eafd 100644
--- a/src/routes/stack/deploy.rs
+++ b/src/routes/stack/deploy.rs
@@ -2,7 +2,7 @@ use crate::configuration::Settings;
 use crate::db;
 use crate::forms::StackPayload;
 use crate::helpers::stack::builder::DcBuilder;
-use crate::helpers::{JsonResponse, MqPool};
+use crate::helpers::{JsonResponse, MqManager};
 use crate::models;
 use actix_web::{post, web, web::Data, Responder, Result};
 use futures_lite::stream::StreamExt;
@@ -18,7 +18,7 @@ pub async fn add(
     user: web::ReqData<Arc<models::User>>,
     path: web::Path<(i32,)>,
     pg_pool: Data<PgPool>,
-    mq_pool: Data<MqPool>,
+    mq_manager: Data<MqManager>,
     sets: Data<Settings>,
 ) -> Result<impl Responder> {
     let id = path.0;
diff --git a/src/startup.rs b/src/startup.rs
index 88312023..b510af64 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -19,8 +19,8 @@ pub async fn run(
     let settings = web::Data::new(settings);
     let pg_pool = web::Data::new(pg_pool);
 
-    let mq_pool = helpers::MqPool::try_new(settings.amqp.connection_string())?;
-    let mq_pool = web::Data::new(mq_pool);
+    let mq_manager = helpers::MqManager::try_new(settings.amqp.connection_string())?;
+    let mq_manager = web::Data::new(mq_manager);
 
     let server = HttpServer::new(move || {
         App::new()
@@ -68,7 +68,7 @@ pub async fn run(
                     .service(crate::routes::stack::update::update),
             )
             .app_data(pg_pool.clone())
-            .app_data(mq_pool.clone())
+            .app_data(mq_manager.clone())
             .app_data(settings.clone())
     })
     .listen(listener)?

From d870fcac78a1ed21f0293b993e2bb5dde1542259 Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 30 Dec 2023 22:13:28 +0200
Subject: [PATCH 109/284] issue-auth mq_manager::publish_and_confirm

---
 src/helpers/mq_manager.rs  | 64 +++++++++++++++++++++++++++++++++++++-
 src/routes/stack/deploy.rs | 51 ++++++------------------------
 2 files changed, 73 insertions(+), 42 deletions(-)

diff --git a/src/helpers/mq_manager.rs b/src/helpers/mq_manager.rs
index 00b95261..ed7a9b17 100644
--- a/src/helpers/mq_manager.rs
+++ b/src/helpers/mq_manager.rs
@@ -1,4 +1,9 @@
 use deadpool_lapin::{Config, CreatePoolError, Pool, Runtime};
+use lapin::{
+    options::*,
+    publisher_confirm::{Confirmation, PublisherConfirm},
+    BasicProperties, Connection, ConnectionProperties,
+};
 
 #[derive(Debug)]
 pub struct MqManager {
@@ -25,5 +30,62 @@ impl MqManager {
         Ok(Self { pool })
     }
 
-    pub async fn create_channel() {}
+    pub async fn publish(
+        &self,
+        exchange: String,
+        routing_key: String,
+        payload: &[u8],
+    ) -> Result<PublisherConfirm, String> {
+        let addr = String::new();
+        Connection::connect(&addr, ConnectionProperties::default())
+            .await
+            .map_err(|err| {
+                tracing::error!("connecting to RabbitMQ {:?}", err);
+                format!("connecting to RabbitMQ {:?}", err)
+            })?
+            .create_channel()
+            .await
+            .map_err(|err| {
+                tracing::error!("creating RabbitMQ channel {:?}", err);
+                format!("creating RabbitMQ channel {:?}", err)
+            })?
+            .basic_publish(
+                "install",
+                routing_key.as_str(),
+                BasicPublishOptions::default(),
+                payload,
+                BasicProperties::default(),
+            )
+            .await
+            .map_err(|err| {
+                tracing::error!("publishing message {:?}", err);
+                format!("publishing message {:?}", err)
+            })
+    }
+
+    pub async fn publish_and_confirm(
+        &self,
+        exchange: String,
+        routing_key: String,
+        payload: &[u8],
+    ) -> Result<(), String> {
+        self.publish(exchange, routing_key, payload)
+            .await
+            .map_err(|err| {
+                tracing::error!("publishing the message {:?}", err);
+                format!("publishing the message {:?}", err)
+            })?
+            .await
+            .map_err(|err| {
+                tracing::error!("confirming the publication {:?}", err);
+                format!("confirming the publication {:?}", err)
+            })
+            .and_then(|confirm| match confirm {
+                Confirmation::NotRequested => {
+                    tracing::error!("confirmation is NotRequested");
+                    Err(format!("confirmation is NotRequested"))
+                }
+                _ => Ok(()),
+            })
+    }
 }
diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs
index dc00eafd..86531193 100644
--- a/src/routes/stack/deploy.rs
+++ b/src/routes/stack/deploy.rs
@@ -6,9 +6,7 @@ use crate::helpers::{JsonResponse, MqManager};
 use crate::models;
 use actix_web::{post, web, web::Data, Responder, Result};
 use futures_lite::stream::StreamExt;
-use lapin::{
-    options::*, publisher_confirm::Confirmation, BasicProperties, Connection, ConnectionProperties,
-};
+use lapin::publisher_confirm::Confirmation;
 use sqlx::PgPool;
 use std::sync::Arc;
 
@@ -51,48 +49,19 @@ pub async fn add(
     let payload = serde_json::to_string::<StackPayload>(&stack_data).map_err(|err| {
         tracing::error!("serializing StackPayload {:?}", err);
         JsonResponse::<models::Stack>::build().internal_server_error("")
-    })?;
-
-    let addr = sets.amqp.connection_string();
-    let routing_key = "install.start.tfa.all.all".to_string();
-    tracing::debug!("Sending message to {:?}", routing_key);
+    })?; //todo is it possible to use lapin serde
 
-    Connection::connect(&addr, ConnectionProperties::default())
-        .await
-        .map_err(|err| {
-            tracing::error!("connecting to RabbitMQ {:?}", err);
-            JsonResponse::<models::Stack>::build().internal_server_error("")
-        })?
-        .create_channel()
-        .await
-        .map_err(|err| {
-            tracing::error!("creating RabbitMQ channel {:?}", err);
-            JsonResponse::<models::Stack>::build().internal_server_error("")
-        })?
-        .basic_publish(
-            "install",
-            routing_key.as_str(),
-            BasicPublishOptions::default(),
+    mq_manager
+        .publish_and_confirm(
+            "install".to_string(),
+            "install.start.tfa.all.all".to_string(),
             payload.as_bytes(),
-            BasicProperties::default(),
         )
         .await
-        .map_err(|err| {
-            tracing::error!("publishing the message {:?}", err);
-            JsonResponse::<models::Stack>::build().internal_server_error("")
-        })?
-        .await
-        .map_err(|err| {
-            tracing::error!("confirming the publication {:?}", err);
-            JsonResponse::<models::Stack>::build().internal_server_error("")
-        })
-        .and_then(|confirm| match confirm {
-            Confirmation::NotRequested => {
-                Err(JsonResponse::<models::Stack>::build()
-                    .bad_request("confirmation is NotRequested"))
-            }
-            _ => Ok(JsonResponse::<models::Stack>::build()
+        .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(err))
+        .map(|_| {
+            JsonResponse::<models::Stack>::build()
                 .set_id(id)
-                .ok("Success")),
+                .ok("Success")
         })
 }

From bf8cadc53164f58bfd48912b313bd1ce6a79856c Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 30 Dec 2023 22:31:13 +0200
Subject: [PATCH 110/284] issue-auth move to deadpool

---
 src/helpers/mq_manager.rs | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)

diff --git a/src/helpers/mq_manager.rs b/src/helpers/mq_manager.rs
index ed7a9b17..898f5af1 100644
--- a/src/helpers/mq_manager.rs
+++ b/src/helpers/mq_manager.rs
@@ -36,13 +36,12 @@ impl MqManager {
         routing_key: String,
         payload: &[u8],
     ) -> Result<PublisherConfirm, String> {
-        let addr = String::new();
-        Connection::connect(&addr, ConnectionProperties::default())
-            .await
-            .map_err(|err| {
-                tracing::error!("connecting to RabbitMQ {:?}", err);
-                format!("connecting to RabbitMQ {:?}", err)
-            })?
+        let connection = self.pool.get().await.map_err(|err| {
+            tracing::error!("getting connection from pool {:?}", err);
+            format!("getting connection from pool {:?}", err)
+        })?;
+
+        connection
             .create_channel()
             .await
             .map_err(|err| {
@@ -50,7 +49,7 @@ impl MqManager {
                 format!("creating RabbitMQ channel {:?}", err)
             })?
             .basic_publish(
-                "install",
+                exchange.as_str(),
                 routing_key.as_str(),
                 BasicPublishOptions::default(),
                 payload,

From 67c2f592ef442150bde618ca8c2ae474ee68f5aa Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 30 Dec 2023 22:40:08 +0200
Subject: [PATCH 111/284] issue_auth mq_manager.create_channel

---
 src/helpers/mq_manager.rs | 38 ++++++++++++++++++++------------------
 1 file changed, 20 insertions(+), 18 deletions(-)

diff --git a/src/helpers/mq_manager.rs b/src/helpers/mq_manager.rs
index 898f5af1..707d4d17 100644
--- a/src/helpers/mq_manager.rs
+++ b/src/helpers/mq_manager.rs
@@ -2,7 +2,7 @@ use deadpool_lapin::{Config, CreatePoolError, Pool, Runtime};
 use lapin::{
     options::*,
     publisher_confirm::{Confirmation, PublisherConfirm},
-    BasicProperties, Connection, ConnectionProperties,
+    BasicProperties, Channel, Connection, ConnectionProperties,
 };
 
 #[derive(Debug)]
@@ -30,24 +30,30 @@ impl MqManager {
         Ok(Self { pool })
     }
 
+    async fn create_channel(&self) -> Result<Channel, String> {
+        self.pool
+            .get()
+            .await
+            .map_err(|err| {
+                tracing::error!("getting connection from pool {:?}", err);
+                format!("getting connection from pool {:?}", err)
+            })?
+            .create_channel()
+            .await
+            .map_err(|err| {
+                tracing::error!("creating RabbitMQ channel {:?}", err);
+                format!("creating RabbitMQ channel {:?}", err)
+            })
+    }
+
     pub async fn publish(
         &self,
         exchange: String,
         routing_key: String,
         payload: &[u8],
     ) -> Result<PublisherConfirm, String> {
-        let connection = self.pool.get().await.map_err(|err| {
-            tracing::error!("getting connection from pool {:?}", err);
-            format!("getting connection from pool {:?}", err)
-        })?;
-
-        connection
-            .create_channel()
-            .await
-            .map_err(|err| {
-                tracing::error!("creating RabbitMQ channel {:?}", err);
-                format!("creating RabbitMQ channel {:?}", err)
-            })?
+        self.create_channel()
+            .await?
             .basic_publish(
                 exchange.as_str(),
                 routing_key.as_str(),
@@ -69,11 +75,7 @@ impl MqManager {
         payload: &[u8],
     ) -> Result<(), String> {
         self.publish(exchange, routing_key, payload)
-            .await
-            .map_err(|err| {
-                tracing::error!("publishing the message {:?}", err);
-                format!("publishing the message {:?}", err)
-            })?
+            .await?
             .await
             .map_err(|err| {
                 tracing::error!("confirming the publication {:?}", err);

From 9ad15f73b173c224b094dce934a9e5852f294de2 Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sat, 30 Dec 2023 22:47:50 +0200
Subject: [PATCH 112/284] issue-auth mq_manager get_connection

---
 src/helpers/mq_manager.rs | 20 +++++++++++---------
 1 file changed, 11 insertions(+), 9 deletions(-)

diff --git a/src/helpers/mq_manager.rs b/src/helpers/mq_manager.rs
index 707d4d17..4fde2617 100644
--- a/src/helpers/mq_manager.rs
+++ b/src/helpers/mq_manager.rs
@@ -1,8 +1,8 @@
-use deadpool_lapin::{Config, CreatePoolError, Pool, Runtime};
+use deadpool_lapin::{Config, CreatePoolError, Object, Pool, Runtime};
 use lapin::{
     options::*,
     publisher_confirm::{Confirmation, PublisherConfirm},
-    BasicProperties, Channel, Connection, ConnectionProperties,
+    BasicProperties, Channel,
 };
 
 #[derive(Debug)]
@@ -30,14 +30,16 @@ impl MqManager {
         Ok(Self { pool })
     }
 
+    async fn get_connection(&self) -> Result<Object, String> {
+        self.pool.get().await.map_err(|err| {
+            tracing::error!("getting connection from pool {:?}", err);
+            format!("getting connection from pool {:?}", err)
+        })
+    }
+
     async fn create_channel(&self) -> Result<Channel, String> {
-        self.pool
-            .get()
-            .await
-            .map_err(|err| {
-                tracing::error!("getting connection from pool {:?}", err);
-                format!("getting connection from pool {:?}", err)
-            })?
+        self.get_connection()
+            .await?
             .create_channel()
             .await
             .map_err(|err| {

From 8d7e9a0136cb25e877411424e0e4bc32350156bf Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sun, 31 Dec 2023 18:34:57 +0200
Subject: [PATCH 113/284] issue-auth tracing::instrument for DcBuilder::build

---
 src/helpers/stack/builder.rs | 130 ++++++++++++++++++-----------------
 1 file changed, 66 insertions(+), 64 deletions(-)

diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index 1db77c54..42df43fa 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -1,12 +1,12 @@
+use crate::forms::{stack, App, StackForm, Volume, Web};
+use crate::helpers::stack::dctypes::{
+    AdvancedVolumes, Compose, ComposeNetwork, ComposeNetworkSettingDetails, ComposeNetworks,
+    ComposeVolume, Entrypoint, Environment, MapOrEmpty, NetworkSettings, Networks, Port, Ports,
+    PublishedPort, Service, Services, SingleValue, TopLevelVolumes, Volumes,
+};
+use crate::models::stack::Stack;
 use indexmap::IndexMap;
-use crate::helpers::stack::dctypes::{Compose, Port, Ports, PublishedPort, Service, Services,
-                                     Volumes, Environment, Entrypoint, AdvancedVolumes, SingleValue,
-                                     Networks, TopLevelVolumes, ComposeVolume, ComposeNetwork,
-                                     ComposeNetworks, MapOrEmpty, ComposeNetworkSettingDetails,
-                                     NetworkSettings};
 use serde_yaml;
-use crate::forms::{StackForm, stack, App, Volume, Web};
-use crate::models::stack::Stack;
 #[derive(Clone, Debug)]
 struct Config {}
 
@@ -16,7 +16,7 @@ impl Default for Config {
     }
 }
 
-impl Default for Port{
+impl Default for Port {
     fn default() -> Self {
         Port {
             target: 80,
@@ -38,10 +38,13 @@ pub struct DcBuilder {
 impl TryInto<AdvancedVolumes> for Volume {
     type Error = String;
     fn try_into(self) -> Result<AdvancedVolumes, Self::Error> {
-
         let source = self.host_path.clone();
         let target = self.container_path.clone();
-        tracing::debug!("Volume conversion result: source: {:?} target: {:?}", source, target);
+        tracing::debug!(
+            "Volume conversion result: source: {:?} target: {:?}",
+            source,
+            target
+        );
         Ok(AdvancedVolumes {
             source: source,
             target: target.unwrap_or("".to_string()),
@@ -57,12 +60,18 @@ impl TryInto<AdvancedVolumes> for Volume {
 impl TryInto<Port> for stack::Port {
     type Error = String;
     fn try_into(self) -> Result<Port, Self::Error> {
-        let cp  = self.container_port.clone()
+        let cp = self
+            .container_port
+            .clone()
             .unwrap_or("".to_string())
-            .parse::<u16>().map_err(|err| "Could not parse port".to_string() )?;
-        let hp = self.host_port.clone()
+            .parse::<u16>()
+            .map_err(|err| "Could not parse port".to_string())?;
+        let hp = self
+            .host_port
+            .clone()
             .unwrap_or("".to_string())
-            .parse::<u16>().map_err(|err| "Could not parse port".to_string() )?;
+            .parse::<u16>()
+            .map_err(|err| "Could not parse port".to_string())?;
 
         tracing::debug!("Port conversion result: cp: {:?} hp: {:?}", cp, hp);
         Ok(Port {
@@ -92,8 +101,7 @@ fn convert_shared_ports(ports: Option<Vec<stack::Port>>) -> Result<Vec<Port>, St
     match ports {
         Some(ports) => {
             tracing::debug!("Ports >>>> {:?}", ports);
-            for port in ports {
-            }
+            for port in ports {}
         }
         None => {
             tracing::debug!("No ports defined by user");
@@ -126,25 +134,22 @@ impl TryIntoService for App {
             ..Default::default()
         };
 
-        let networks: Networks = self.network
-            .clone()
-            .try_into()
-            .unwrap_or_default();
+        let networks: Networks = self.network.clone().try_into().unwrap_or_default();
 
-        let ports: Vec<Port> = self.ports
+        let ports: Vec<Port> = self
+            .ports
             .clone()
             .unwrap_or_default()
             .into_iter()
             .map(|x| x.try_into().unwrap())
             .collect();
 
-        let volumes: Vec<AdvancedVolumes> = self.volumes
+        let volumes: Vec<AdvancedVolumes> = self
+            .volumes
             .clone()
             .unwrap_or_default()
             .into_iter()
-            .map(|x| {
-                x.try_into().unwrap()
-            })
+            .map(|x| x.try_into().unwrap())
             .collect();
 
         let mut envs = IndexMap::new();
@@ -152,7 +157,7 @@ impl TryIntoService for App {
             let items = item
                 .into_iter()
                 .map(|(k, v)| (k, Some(SingleValue::String(v.clone()))))
-                .collect::<IndexMap<_,_>>();
+                .collect::<IndexMap<_, _>>();
 
             envs.extend(items);
         }
@@ -169,7 +174,6 @@ impl TryIntoService for App {
 
 impl Into<IndexMap<String, MapOrEmpty<NetworkSettings>>> for stack::ComposeNetworks {
     fn into(self) -> IndexMap<String, MapOrEmpty<NetworkSettings>> {
-
         // tracing::debug!("networks found {:?}", self.networks);
         let mut networks = vec!["default_network".to_string()];
         if self.networks.is_some() {
@@ -177,59 +181,58 @@ impl Into<IndexMap<String, MapOrEmpty<NetworkSettings>>> for stack::ComposeNetwo
         }
         let networks = networks
             .into_iter()
-            .map(|net|
-                (net,
-                 MapOrEmpty::Map(
-                     NetworkSettings {
-                         attachable: false,
-                         driver: None,
-                         driver_opts: Default::default(),
-                         enable_ipv6: false,
-                         internal: false,
-                         // external: None,
-                         external: Some(ComposeNetwork::Bool(true)),
-                         ipam: None,
-                         labels: Default::default(),
-                         name: Some("default".to_string()),
-                     }
-                 ))
-            )
+            .map(|net| {
+                (
+                    net,
+                    MapOrEmpty::Map(NetworkSettings {
+                        attachable: false,
+                        driver: None,
+                        driver_opts: Default::default(),
+                        enable_ipv6: false,
+                        internal: false,
+                        // external: None,
+                        external: Some(ComposeNetwork::Bool(true)),
+                        ipam: None,
+                        labels: Default::default(),
+                        name: Some("default".to_string()),
+                    }),
+                )
+            })
             .collect::<IndexMap<String, _>>();
         networks
     }
 }
 
-
 pub fn extract_named_volumes(app: App) -> IndexMap<String, MapOrEmpty<ComposeVolume>> {
-
     let mut named_volumes = IndexMap::default();
 
-        let volumes = app.volumes
-            .unwrap()
-            .into_iter()
-            .filter(|volume| is_named_docker_volume(
-                volume.host_path.clone().unwrap().as_str())
-            )
-            .map(|volume| {
-                let k = volume.host_path.clone().unwrap();
-                (k.clone(), MapOrEmpty::Map(ComposeVolume {
+    let volumes = app
+        .volumes
+        .unwrap()
+        .into_iter()
+        .filter(|volume| is_named_docker_volume(volume.host_path.clone().unwrap().as_str()))
+        .map(|volume| {
+            let k = volume.host_path.clone().unwrap();
+            (
+                k.clone(),
+                MapOrEmpty::Map(ComposeVolume {
                     driver: None,
                     driver_opts: Default::default(),
                     external: None,
                     labels: Default::default(),
-                    name: Some(k.clone())
-                }))
-            })
-            .collect::<IndexMap<String, MapOrEmpty<ComposeVolume>>>();
+                    name: Some(k.clone()),
+                }),
+            )
+        })
+        .collect::<IndexMap<String, MapOrEmpty<ComposeVolume>>>();
 
-        named_volumes.extend(volumes);
+    named_volumes.extend(volumes);
     // tracing::debug!("Named volumes: {:?}", named_volumes);
 
     named_volumes
 }
 
 impl DcBuilder {
-
     pub fn new(stack: Stack) -> Self {
         DcBuilder {
             config: Config::default(),
@@ -237,6 +240,7 @@ impl DcBuilder {
         }
     }
 
+    #[tracing::instrument(name = "building stack")]
     pub fn build(&self) -> Option<String> {
         tracing::debug!("Start build docker compose from {:?}", &self.stack.body);
         let mut compose_content = Compose {
@@ -277,7 +281,6 @@ impl DcBuilder {
                 if !named_volumes.is_empty() {
                     compose_content.volumes = TopLevelVolumes(named_volumes);
                 }
-
             }
             Err(e) => {
                 tracing::debug!("Unpack stack form error {:?}", e);
@@ -286,8 +289,7 @@ impl DcBuilder {
         tracing::debug!("services {:?}", &services);
         compose_content.services = Services(services);
 
-
-        let fname= format!("./files/{}.yml", self.stack.stack_id);
+        let fname = format!("./files/{}.yml", self.stack.stack_id);
         tracing::debug!("Saving docker compose to file {:?}", fname);
         let target_file = std::path::Path::new(fname.as_str());
         // serialize to string

From ea374d7d054e81da977565812c1ee5d6b18631ef Mon Sep 17 00:00:00 2001
From: Petru <smart__petea@mail.ru>
Date: Sun, 31 Dec 2023 19:04:34 +0200
Subject: [PATCH 114/284] issue-auth DcBuilder build()

---
 src/helpers/stack/builder.rs | 19 +++++++++++--------
 1 file changed, 11 insertions(+), 8 deletions(-)

diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index 42df43fa..5a42b75b 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -206,8 +206,12 @@ impl Into<IndexMap<String, MapOrEmpty<NetworkSettings>>> for stack::ComposeNetwo
 pub fn extract_named_volumes(app: App) -> IndexMap<String, MapOrEmpty<ComposeVolume>> {
     let mut named_volumes = IndexMap::default();
 
-    let volumes = app
-        .volumes
+    let volumes = app.volumes;
+    if volumes.is_none() {
+        return named_volumes;
+    }
+
+    let volumes = volumes
         .unwrap()
         .into_iter()
         .filter(|volume| is_named_docker_volume(volume.host_path.clone().unwrap().as_str()))
@@ -293,12 +297,11 @@ impl DcBuilder {
         tracing::debug!("Saving docker compose to file {:?}", fname);
         let target_file = std::path::Path::new(fname.as_str());
         // serialize to string
-        let serialized = match serde_yaml::to_string(&compose_content) {
-            Ok(s) => s,
-            Err(e) => panic!("Failed to serialize docker-compose file: {}", e),
-        };
-        // serialize to file
-        std::fs::write(target_file, serialized.clone()).unwrap();
+        let serialized = serde_yaml::to_string(&compose_content)
+            .map_err(|err| panic!("Failed to serialize docker-compose file: {}", err))
+            .unwrap();
+
+        std::fs::write(target_file, serialized.clone()).map_err(|err| panic!("{}", err));
 
         Some(serialized)
     }

From fb7b13203b682efb19fe43185c23f8d2c22a1a46 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 6 Jan 2024 15:09:36 +0200
Subject: [PATCH 115/284] issue-auth mq_manager. publish. Serialize

---
 src/helpers/mq_manager.rs  | 17 +++++++++++------
 src/routes/stack/deploy.rs |  7 +------
 2 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/src/helpers/mq_manager.rs b/src/helpers/mq_manager.rs
index 4fde2617..5b058ea4 100644
--- a/src/helpers/mq_manager.rs
+++ b/src/helpers/mq_manager.rs
@@ -4,6 +4,7 @@ use lapin::{
     publisher_confirm::{Confirmation, PublisherConfirm},
     BasicProperties, Channel,
 };
+use serde::ser::Serialize;
 
 #[derive(Debug)]
 pub struct MqManager {
@@ -48,19 +49,23 @@ impl MqManager {
             })
     }
 
-    pub async fn publish(
+    pub async fn publish<T: ?Sized + Serialize>(
         &self,
         exchange: String,
         routing_key: String,
-        payload: &[u8],
+        msg: &T,
     ) -> Result<PublisherConfirm, String> {
+        let payload = serde_json::to_string::<T>(msg).map_err(|err| {
+            format!("{:?}", err)
+        })?;
+
         self.create_channel()
             .await?
             .basic_publish(
                 exchange.as_str(),
                 routing_key.as_str(),
                 BasicPublishOptions::default(),
-                payload,
+                payload.as_bytes(),
                 BasicProperties::default(),
             )
             .await
@@ -70,13 +75,13 @@ impl MqManager {
             })
     }
 
-    pub async fn publish_and_confirm(
+    pub async fn publish_and_confirm<T: ?Sized + Serialize>(
         &self,
         exchange: String,
         routing_key: String,
-        payload: &[u8],
+        msg: &T
     ) -> Result<(), String> {
-        self.publish(exchange, routing_key, payload)
+        self.publish(exchange, routing_key, msg)
             .await?
             .await
             .map_err(|err| {
diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs
index 86531193..f626cc07 100644
--- a/src/routes/stack/deploy.rs
+++ b/src/routes/stack/deploy.rs
@@ -46,16 +46,11 @@ pub async fn add(
     stack_data.user_email = Some(user.email.clone());
     stack_data.stack_code = stack_data.custom.custom_stack_code.clone();
 
-    let payload = serde_json::to_string::<StackPayload>(&stack_data).map_err(|err| {
-        tracing::error!("serializing StackPayload {:?}", err);
-        JsonResponse::<models::Stack>::build().internal_server_error("")
-    })?; //todo is it possible to use lapin serde
-
     mq_manager
         .publish_and_confirm(
             "install".to_string(),
             "install.start.tfa.all.all".to_string(),
-            payload.as_bytes(),
+            &stack_data,
         )
         .await
         .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(err))

From 6ddd62ccef1363159ae9d73fbab93d5ddb7ec7e7 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 6 Jan 2024 21:44:03 +0200
Subject: [PATCH 116/284] issue-auth DcBuilder stack. try from

---
 src/forms/mod.rs             |  4 +--
 src/forms/stack.rs           | 33 ----------------------
 src/forms/stack_payload.rs   | 54 ++++++++++++++++++++++++++++++++++++
 src/helpers/stack/builder.rs |  6 ++--
 src/routes/stack/deploy.rs   | 12 ++------
 5 files changed, 62 insertions(+), 47 deletions(-)
 create mode 100644 src/forms/stack_payload.rs

diff --git a/src/forms/mod.rs b/src/forms/mod.rs
index ae65e1c4..cee99fef 100644
--- a/src/forms/mod.rs
+++ b/src/forms/mod.rs
@@ -1,8 +1,8 @@
 mod rating;
-
 pub mod stack;
 pub mod user;
+mod stack_payload;
 
 pub use rating::*;
-
 pub use stack::*;
+pub use stack_payload::*;
diff --git a/src/forms/stack.rs b/src/forms/stack.rs
index cc322330..febd2af9 100644
--- a/src/forms/stack.rs
+++ b/src/forms/stack.rs
@@ -125,39 +125,6 @@ pub struct StackForm {
     pub custom: Custom,
 }
 
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-#[serde(rename_all = "snake_case")]
-pub struct StackPayload {
-    pub(crate) id: Option<i32>,
-    pub(crate) user_token: Option<String>,
-    pub(crate) user_email: Option<String>,
-    #[serde(rename = "commonDomain")]
-    pub common_domain: String,
-    pub domain_list: Option<DomainList>,
-    pub region: String,
-    pub zone: Option<String>,
-    pub server: String,
-    pub os: String,
-    pub ssl: String,
-    pub vars: Option<Vec<Var>>,
-    #[serde(rename = "integrated_features")]
-    pub integrated_features: Option<Vec<Value>>,
-    #[serde(rename = "extended_features")]
-    pub extended_features: Option<Vec<Value>>,
-    pub subscriptions: Option<Vec<String>>,
-    pub form_app: Option<Vec<String>>,
-    pub disk_type: Option<String>,
-    #[serde(rename = "save_token")]
-    pub save_token: bool,
-    #[serde(rename = "cloud_token")]
-    pub cloud_token: String,
-    pub provider: String,
-    pub stack_code: String,
-    #[serde(rename = "selected_plan")]
-    pub selected_plan: String,
-    pub custom: Custom,
-}
-
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 #[serde(rename_all = "camelCase")]
 pub struct DomainList {}
diff --git a/src/forms/stack_payload.rs b/src/forms/stack_payload.rs
new file mode 100644
index 00000000..521642d5
--- /dev/null
+++ b/src/forms/stack_payload.rs
@@ -0,0 +1,54 @@
+use std::convert::TryFrom;
+use crate::models;
+use crate::forms;
+use serde_json::Value;
+use serde::{Deserialize, Serialize};
+use serde_valid::Validate;
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+#[serde(rename_all = "snake_case")]
+pub struct StackPayload {
+    pub(crate) id: Option<i32>,
+    pub(crate) user_token: Option<String>,
+    pub(crate) user_email: Option<String>,
+    #[serde(rename = "commonDomain")]
+    pub common_domain: String,
+    pub domain_list: Option<forms::stack::DomainList>,
+    pub region: String,
+    pub zone: Option<String>,
+    pub server: String,
+    pub os: String,
+    pub ssl: String,
+    pub vars: Option<Vec<forms::stack::Var>>,
+    #[serde(rename = "integrated_features")]
+    pub integrated_features: Option<Vec<Value>>,
+    #[serde(rename = "extended_features")]
+    pub extended_features: Option<Vec<Value>>,
+    pub subscriptions: Option<Vec<String>>,
+    pub form_app: Option<Vec<String>>,
+    pub disk_type: Option<String>,
+    #[serde(rename = "save_token")]
+    pub save_token: bool,
+    #[serde(rename = "cloud_token")]
+    pub cloud_token: String,
+    pub provider: String,
+    pub stack_code: String,
+    #[serde(rename = "selected_plan")]
+    pub selected_plan: String,
+    pub custom: forms::stack::Custom,
+}
+
+impl TryFrom<&models::Stack> for StackPayload {
+    type Error = String;
+
+    fn try_from(stack: &models::Stack) -> Result<Self, Self::Error> {
+        let mut stack_data = serde_json::from_value::<StackPayload>(stack.body.clone()).map_err(|err| {
+            format!("{:?}", err)
+        })?;
+
+        stack_data.id = Some(stack.id.clone());
+        stack_data.stack_code = stack_data.custom.custom_stack_code.clone();
+
+        Ok(stack_data)
+    }
+}
diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index 5a42b75b..eeca4a2a 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -4,7 +4,7 @@ use crate::helpers::stack::dctypes::{
     ComposeVolume, Entrypoint, Environment, MapOrEmpty, NetworkSettings, Networks, Port, Ports,
     PublishedPort, Service, Services, SingleValue, TopLevelVolumes, Volumes,
 };
-use crate::models::stack::Stack;
+use crate::models;
 use indexmap::IndexMap;
 use serde_yaml;
 #[derive(Clone, Debug)]
@@ -32,7 +32,7 @@ impl Default for Port {
 #[derive(Clone, Debug)]
 pub struct DcBuilder {
     config: Config,
-    pub(crate) stack: Stack,
+    pub(crate) stack: models::Stack,
 }
 
 impl TryInto<AdvancedVolumes> for Volume {
@@ -237,7 +237,7 @@ pub fn extract_named_volumes(app: App) -> IndexMap<String, MapOrEmpty<ComposeVol
 }
 
 impl DcBuilder {
-    pub fn new(stack: Stack) -> Self {
+    pub fn new(stack: models::Stack) -> Self {
         DcBuilder {
             config: Config::default(),
             stack,
diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs
index f626cc07..80f49858 100644
--- a/src/routes/stack/deploy.rs
+++ b/src/routes/stack/deploy.rs
@@ -1,6 +1,6 @@
 use crate::configuration::Settings;
 use crate::db;
-use crate::forms::StackPayload;
+use crate::forms;
 use crate::helpers::stack::builder::DcBuilder;
 use crate::helpers::{JsonResponse, MqManager};
 use crate::models;
@@ -35,16 +35,10 @@ pub async fn add(
         JsonResponse::<models::Stack>::build().internal_server_error("")
     })?;
 
-    let mut stack_data =
-        serde_json::from_value::<StackPayload>(dc.stack.body.clone()).map_err(|err| {
-            tracing::error!("transforming json Value into StackPayload {:?}", err);
-            JsonResponse::<models::Stack>::build().bad_request("")
-        })?;
-
-    stack_data.id = Some(id);
+    let mut stack_data = forms::StackPayload::try_from(&dc.stack)
+        .map_err(|err| JsonResponse::<models::Stack>::build().bad_request(err))?;
     stack_data.user_token = Some(user.id.clone());
     stack_data.user_email = Some(user.email.clone());
-    stack_data.stack_code = stack_data.custom.custom_stack_code.clone();
 
     mq_manager
         .publish_and_confirm(

From 0c8936690caf53e6e3e31ff7ca5039460be21c92 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sun, 7 Jan 2024 08:51:14 +0200
Subject: [PATCH 117/284] issue-auth Option->Result for DcBuilder::build

---
 src/helpers/stack/builder.rs | 74 ++++++++++++++++--------------------
 src/routes/stack/compose.rs  | 18 +++++----
 src/routes/stack/deploy.rs   |  5 +--
 3 files changed, 45 insertions(+), 52 deletions(-)

diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index eeca4a2a..1b7c0a9e 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -124,7 +124,7 @@ fn is_named_docker_volume(volume: &str) -> bool {
 }
 
 trait TryIntoService {
-    fn try_into_service(&self) -> Service;
+    fn try_into_service(&self) -> Service; //todo transform into try_into
 }
 
 impl TryIntoService for App {
@@ -245,64 +245,56 @@ impl DcBuilder {
     }
 
     #[tracing::instrument(name = "building stack")]
-    pub fn build(&self) -> Option<String> {
-        tracing::debug!("Start build docker compose from {:?}", &self.stack.body);
+    pub fn build(&self) -> Result<String, String> {
         let mut compose_content = Compose {
             version: Some("3.8".to_string()),
             ..Default::default()
         };
-        let _stack = serde_json::from_value::<StackForm>(self.stack.body.clone());
+        let apps = serde_json::from_value::<StackForm>(self.stack.body.clone())
+            .map_err(|err| format!("{:?}", err))?;
         let mut services = IndexMap::new();
         let mut named_volumes = IndexMap::default();
 
-        match _stack {
-            Ok(apps) => {
-                for app_type in &apps.custom.web {
-                    let service = app_type.app.try_into_service();
-                    services.insert(app_type.app.code.clone().to_owned(), Some(service));
-                    named_volumes.extend(extract_named_volumes(app_type.app.clone()));
-                }
-
-                if let Some(srvs) = apps.custom.service {
-                    for app_type in srvs {
-                        let service = app_type.app.try_into_service();
-                        services.insert(app_type.app.code.clone().to_owned(), Some(service));
-                        named_volumes.extend(extract_named_volumes(app_type.app.clone()));
-                    }
-                }
-
-                if let Some(features) = apps.custom.feature {
-                    for app_type in features {
-                        let service = app_type.app.try_into_service();
-                        services.insert(app_type.app.code.clone().to_owned(), Some(service));
-                        named_volumes.extend(extract_named_volumes(app_type.app.clone()));
-                    }
-                }
-
-                let networks = apps.custom.networks.clone();
-                compose_content.networks = ComposeNetworks(networks.into());
-
-                if !named_volumes.is_empty() {
-                    compose_content.volumes = TopLevelVolumes(named_volumes);
-                }
+        for app_type in &apps.custom.web {
+            let service = app_type.app.try_into_service(); //todo
+            services.insert(app_type.app.code.clone().to_owned(), Some(service));
+            named_volumes.extend(extract_named_volumes(app_type.app.clone()));
+        }
+
+        if let Some(srvs) = apps.custom.service {
+            for app_type in srvs {
+                let service = app_type.app.try_into_service(); //todo
+                services.insert(app_type.app.code.clone().to_owned(), Some(service));
+                named_volumes.extend(extract_named_volumes(app_type.app.clone()));
             }
-            Err(e) => {
-                tracing::debug!("Unpack stack form error {:?}", e);
+        }
+
+        if let Some(features) = apps.custom.feature {
+            for app_type in features {
+                let service = app_type.app.try_into_service(); //todo
+                services.insert(app_type.app.code.clone().to_owned(), Some(service));
+                named_volumes.extend(extract_named_volumes(app_type.app.clone()));
             }
         }
+
+        let networks = apps.custom.networks.clone();
+        compose_content.networks = ComposeNetworks(networks.into());
+
+        if !named_volumes.is_empty() {
+            compose_content.volumes = TopLevelVolumes(named_volumes);
+        }
+
         tracing::debug!("services {:?}", &services);
         compose_content.services = Services(services);
 
         let fname = format!("./files/{}.yml", self.stack.stack_id);
         tracing::debug!("Saving docker compose to file {:?}", fname);
         let target_file = std::path::Path::new(fname.as_str());
-        // serialize to string
         let serialized = serde_yaml::to_string(&compose_content)
-            .map_err(|err| panic!("Failed to serialize docker-compose file: {}", err))
-            .unwrap();
+            .map_err(|err| format!("Failed to serialize docker-compose file: {}", err))?;
 
-        std::fs::write(target_file, serialized.clone()).map_err(|err| panic!("{}", err));
+        std::fs::write(target_file, serialized.clone()).map_err(|err| format!("{}", err))?;
 
-        Some(serialized)
+        Ok(serialized)
     }
 }
diff --git a/src/routes/stack/compose.rs b/src/routes/stack/compose.rs
index 4e58141e..b8b51233 100644
--- a/src/routes/stack/compose.rs
+++ b/src/routes/stack/compose.rs
@@ -26,10 +26,11 @@ pub async fn add(
         })?;
 
     let id = stack.id.clone();
-    let fc = DcBuilder::new(stack).build().ok_or_else(|| {
-        tracing::error!("Error. Compose builder returned an empty string");
-        JsonResponse::<models::Stack>::build().internal_server_error("troubles at building")
-    })?;
+    let fc = DcBuilder::new(stack)
+        .build()
+        .map_err(|err| {
+            JsonResponse::<models::Stack>::build().internal_server_error(err)
+        })?;
 
     Ok(JsonResponse::build().set_id(id).set_item(fc).ok("Success"))
 }
@@ -52,10 +53,11 @@ pub async fn admin(
         })?;
 
     let id = stack.id.clone();
-    let fc = DcBuilder::new(stack).build().ok_or_else(|| {
-        tracing::error!("Error. Compose builder returned an empty string");
-        JsonResponse::<models::Stack>::build().internal_server_error("troubles at building")
-    })?;
+    let fc = DcBuilder::new(stack)
+        .build()
+        .map_err(|err| {
+            JsonResponse::<models::Stack>::build().internal_server_error(err)
+        })?;
 
     Ok(JsonResponse::build().set_id(id).set_item(fc).ok("Success"))
 }
diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs
index 80f49858..005ac531 100644
--- a/src/routes/stack/deploy.rs
+++ b/src/routes/stack/deploy.rs
@@ -30,9 +30,8 @@ pub async fn add(
 
     let id = stack.id.clone();
     let dc = DcBuilder::new(stack);
-    dc.build().ok_or_else(|| {
-        tracing::error!("Error. Compose builder returned an empty string");
-        JsonResponse::<models::Stack>::build().internal_server_error("")
+    dc.build().map_err(|err| {
+        JsonResponse::<models::Stack>::build().internal_server_error(err)
     })?;
 
     let mut stack_data = forms::StackPayload::try_from(&dc.stack)

From 3bad20d8927ee81c9e2a8b1b05ca9667e79344e6 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sun, 7 Jan 2024 09:48:53 +0200
Subject: [PATCH 118/284] issue-auth StackForm::try_from

---
 src/forms/mod.rs             |  2 ++
 src/forms/stack.rs           | 46 ---------------------------
 src/forms/stack_form.rs      | 61 ++++++++++++++++++++++++++++++++++++
 src/helpers/stack/builder.rs |  4 +--
 src/routes/stack/add.rs      | 14 ++++-----
 src/routes/stack/update.rs   |  8 ++---
 6 files changed, 76 insertions(+), 59 deletions(-)
 create mode 100644 src/forms/stack_form.rs

diff --git a/src/forms/mod.rs b/src/forms/mod.rs
index cee99fef..a1379dec 100644
--- a/src/forms/mod.rs
+++ b/src/forms/mod.rs
@@ -2,7 +2,9 @@ mod rating;
 pub mod stack;
 pub mod user;
 mod stack_payload;
+mod stack_form;
 
 pub use rating::*;
 pub use stack::*;
 pub use stack_payload::*;
+pub use stack_form::*;
diff --git a/src/forms/stack.rs b/src/forms/stack.rs
index febd2af9..a9fc3ccc 100644
--- a/src/forms/stack.rs
+++ b/src/forms/stack.rs
@@ -79,52 +79,6 @@ impl AsRef<DockerImage> for App {
     }
 }
 
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct StackForm {
-    // #[validate(min_length=2)]
-    // #[validate(max_length=255)]
-    #[serde(rename = "commonDomain")]
-    pub common_domain: Option<String>,
-    pub domain_list: Option<DomainList>,
-    #[validate(min_length = 2)]
-    #[validate(max_length = 255)]
-    pub stack_code: Option<String>,
-    #[validate(min_length = 2)]
-    #[validate(max_length = 50)]
-    pub region: String,
-    #[validate(min_length = 2)]
-    #[validate(max_length = 50)]
-    pub zone: Option<String>,
-    #[validate(min_length = 2)]
-    #[validate(max_length = 50)]
-    pub server: String,
-    #[validate(min_length = 2)]
-    #[validate(max_length = 50)]
-    pub os: String,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 50)]
-    pub ssl: String,
-    pub vars: Option<Vec<Var>>,
-    pub integrated_features: Option<Vec<Value>>,
-    pub extended_features: Option<Vec<Value>>,
-    pub subscriptions: Option<Vec<String>>,
-    pub form_app: Option<Vec<String>>,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 50)]
-    pub disk_type: Option<String>,
-    pub save_token: bool,
-    #[validate(min_length = 10)]
-    #[validate(max_length = 255)]
-    pub cloud_token: String,
-    #[validate(min_length = 2)]
-    #[validate(max_length = 50)]
-    pub provider: String,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 50)]
-    pub selected_plan: String,
-    pub custom: Custom,
-}
-
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 #[serde(rename_all = "camelCase")]
 pub struct DomainList {}
diff --git a/src/forms/stack_form.rs b/src/forms/stack_form.rs
new file mode 100644
index 00000000..989205af
--- /dev/null
+++ b/src/forms/stack_form.rs
@@ -0,0 +1,61 @@
+use serde::{Deserialize, Serialize};
+use serde_json::Value;
+use serde_valid::Validate;
+use std::collections::HashMap;
+use std::fmt;
+use crate::models;
+use crate::forms;
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct StackForm {
+    // #[validate(min_length=2)]
+    // #[validate(max_length=255)]
+    #[serde(rename = "commonDomain")]
+    pub common_domain: Option<String>,
+    pub domain_list: Option<forms::DomainList>,
+    #[validate(min_length = 2)]
+    #[validate(max_length = 255)]
+    pub stack_code: Option<String>,
+    #[validate(min_length = 2)]
+    #[validate(max_length = 50)]
+    pub region: String,
+    #[validate(min_length = 2)]
+    #[validate(max_length = 50)]
+    pub zone: Option<String>,
+    #[validate(min_length = 2)]
+    #[validate(max_length = 50)]
+    pub server: String,
+    #[validate(min_length = 2)]
+    #[validate(max_length = 50)]
+    pub os: String,
+    #[validate(min_length = 3)]
+    #[validate(max_length = 50)]
+    pub ssl: String,
+    pub vars: Option<Vec<forms::Var>>,
+    pub integrated_features: Option<Vec<Value>>,
+    pub extended_features: Option<Vec<Value>>,
+    pub subscriptions: Option<Vec<String>>,
+    pub form_app: Option<Vec<String>>,
+    #[validate(min_length = 3)]
+    #[validate(max_length = 50)]
+    pub disk_type: Option<String>,
+    pub save_token: bool,
+    #[validate(min_length = 10)]
+    #[validate(max_length = 255)]
+    pub cloud_token: String,
+    #[validate(min_length = 2)]
+    #[validate(max_length = 50)]
+    pub provider: String,
+    #[validate(min_length = 3)]
+    #[validate(max_length = 50)]
+    pub selected_plan: String,
+    pub custom: forms::Custom,
+}
+
+impl TryFrom<&models::Stack> for StackForm {
+    type Error = String;
+
+    fn try_from(stack: &models::Stack) -> Result<Self, Self::Error> {
+        serde_json::from_value::<StackForm>(stack.body.clone()).map_err(|err| format!("{:?}", err))
+    }
+}
diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index 1b7c0a9e..ea91da78 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -250,8 +250,8 @@ impl DcBuilder {
             version: Some("3.8".to_string()),
             ..Default::default()
         };
-        let apps = serde_json::from_value::<StackForm>(self.stack.body.clone())
-            .map_err(|err| format!("{:?}", err))?;
+
+        let apps = StackForm::try_from(&self.stack)?; 
         let mut services = IndexMap::new();
         let mut named_volumes = IndexMap::default();
 
diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs
index 7ed5a09b..bcf281c7 100644
--- a/src/routes/stack/add.rs
+++ b/src/routes/stack/add.rs
@@ -1,5 +1,5 @@
 use crate::db;
-use crate::forms::stack::StackForm;
+use crate::forms;
 use crate::helpers::JsonResponse;
 use crate::models;
 use actix_web::Error;
@@ -27,8 +27,8 @@ pub async fn add(
 
     check_if_stack_exists(pg_pool.get_ref(), &stack_name).await?;
 
-    let body: Value = serde_json::to_value::<StackForm>(form)
-        .or(serde_json::to_value::<StackForm>(StackForm::default()))
+    let body: Value = serde_json::to_value::<forms::StackForm>(form)
+        .or(serde_json::to_value::<forms::StackForm>(forms::StackForm::default()))
         .unwrap();
 
     let stack = models::Stack::new(user.id.clone(), stack_name, body);
@@ -53,17 +53,17 @@ async fn check_if_stack_exists(pg_pool: &PgPool, stack_name: &String) -> Result<
         })
 }
 
-async fn body_into_form(body: Bytes) -> Result<StackForm, Error> {
+async fn body_into_form(body: Bytes) -> Result<forms::StackForm, Error> {
     let body_bytes = actix_web::body::to_bytes(body).await.unwrap();
     let body_str = str::from_utf8(&body_bytes)
-        .map_err(|err| JsonResponse::<StackForm>::build().internal_server_error(err.to_string()))?;
+        .map_err(|err| JsonResponse::<forms::StackForm>::build().internal_server_error(err.to_string()))?;
     let deserializer = &mut serde_json::Deserializer::from_str(body_str);
     serde_path_to_error::deserialize(deserializer)
         .map_err(|err| {
             let msg = format!("{}:{:?}", err.path().to_string(), err);
-            JsonResponse::<StackForm>::build().bad_request(msg)
+            JsonResponse::<forms::StackForm>::build().bad_request(msg)
         })
-        .and_then(|form: StackForm| {
+        .and_then(|form: forms::StackForm| {
             if !form.validate().is_ok() {
                 let errors = form.validate().unwrap_err();
                 let err_msg = format!("Invalid data received {:?}", &errors.to_string());
diff --git a/src/routes/stack/update.rs b/src/routes/stack/update.rs
index 229553f7..a22be0c2 100644
--- a/src/routes/stack/update.rs
+++ b/src/routes/stack/update.rs
@@ -1,4 +1,4 @@
-use crate::forms::stack::StackForm;
+use crate::forms;
 use crate::helpers::JsonResponse;
 use crate::models;
 use crate::models::user::User;
@@ -16,7 +16,7 @@ use uuid::Uuid;
 #[post("/{id}")]
 pub async fn update(
     path: web::Path<(i32,)>,
-    form: web::Json<StackForm>,
+    form: web::Json<forms::StackForm>,
     user: web::ReqData<Arc<User>>,
     pg_pool: Data<PgPool>,
 ) -> Result<impl Responder> {
@@ -67,11 +67,11 @@ pub async fn update(
         return Err(JsonResponse::<models::Stack>::build().bad_request(errors.to_string()));
     }
 
-    let body: Value = match serde_json::to_value::<StackForm>(form.into_inner()) {
+    let body: Value = match serde_json::to_value::<forms::StackForm>(form.into_inner()) {
         Ok(body) => body,
         Err(err) => {
             tracing::error!("Request_id {} error unwrap body {:?}", request_id, err);
-            serde_json::to_value::<StackForm>(StackForm::default()).unwrap()
+            serde_json::to_value::<forms::StackForm>(forms::StackForm::default()).unwrap()
         }
     };
 

From 27e66b97acd49e65bbcf488d86bd2656edfb87ca Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sun, 7 Jan 2024 22:58:15 +0200
Subject: [PATCH 119/284] issue-auth Networks::try_from

---
 src/helpers/stack/builder.rs | 44 +++++++++++++++++++-----------------
 1 file changed, 23 insertions(+), 21 deletions(-)

diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index ea91da78..4acf9631 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -84,14 +84,18 @@ impl TryInto<Port> for stack::Port {
     }
 }
 
-impl TryInto<Networks> for stack::ServiceNetworks {
+impl TryFrom<&stack::ServiceNetworks> for Networks {
     type Error = ();
-    fn try_into(self) -> Result<Networks, Self::Error> {
-        let mut networks = vec!["default_network".to_string()];
-        if self.network.is_some() {
-            networks.append(&mut self.network.unwrap());
-        }
-        Ok(Networks::Simple(networks))
+
+    fn try_from(serviceNetworks: &stack::ServiceNetworks) -> Result<Networks, Self::Error> {
+        let mut result = vec!["default_network".to_string()];
+        serviceNetworks.network.as_ref().map(|networks| {
+            for n in networks {
+                result.push(n.to_string());
+            }
+        });
+
+        Ok(Networks::Simple(result))
     }
 }
 
@@ -123,20 +127,18 @@ fn is_named_docker_volume(volume: &str) -> bool {
     is_alphanumeric && does_not_contain_slash
 }
 
-trait TryIntoService {
-    fn try_into_service(&self) -> Service; //todo transform into try_into
-}
+impl TryFrom<&App> for Service {
+    type Error = String;
 
-impl TryIntoService for App {
-    fn try_into_service(&self) -> Service {
+    fn try_from(app: &App) -> Result<Self, Self::Error> {
         let mut service = Service {
-            image: Some(self.docker_image.to_string()),
+            image: Some(app.docker_image.to_string()),
             ..Default::default()
         };
 
-        let networks: Networks = self.network.clone().try_into().unwrap_or_default();
+        let networks = Networks::try_from(&app.network).unwrap_or_default();
 
-        let ports: Vec<Port> = self
+        let ports: Vec<Port> = app //todo
             .ports
             .clone()
             .unwrap_or_default()
@@ -144,7 +146,7 @@ impl TryIntoService for App {
             .map(|x| x.try_into().unwrap())
             .collect();
 
-        let volumes: Vec<AdvancedVolumes> = self
+        let volumes: Vec<AdvancedVolumes> = app //todo
             .volumes
             .clone()
             .unwrap_or_default()
@@ -153,7 +155,7 @@ impl TryIntoService for App {
             .collect();
 
         let mut envs = IndexMap::new();
-        for item in self.environment.environment.clone().unwrap_or_default() {
+        for item in app.environment.environment.clone().unwrap_or_default() { //todo
             let items = item
                 .into_iter()
                 .map(|(k, v)| (k, Some(SingleValue::String(v.clone()))))
@@ -168,7 +170,7 @@ impl TryIntoService for App {
         service.volumes = Volumes::Advanced(volumes);
         service.environment = Environment::KvPair(envs);
 
-        service
+        Ok(service)
     }
 }
 
@@ -256,14 +258,14 @@ impl DcBuilder {
         let mut named_volumes = IndexMap::default();
 
         for app_type in &apps.custom.web {
-            let service = app_type.app.try_into_service(); //todo
+            let service = Service::try_from(&app_type.app)?;
             services.insert(app_type.app.code.clone().to_owned(), Some(service));
             named_volumes.extend(extract_named_volumes(app_type.app.clone()));
         }
 
         if let Some(srvs) = apps.custom.service {
             for app_type in srvs {
-                let service = app_type.app.try_into_service(); //todo
+                let service = Service::try_from(&app_type.app)?;
                 services.insert(app_type.app.code.clone().to_owned(), Some(service));
                 named_volumes.extend(extract_named_volumes(app_type.app.clone()));
             }
@@ -271,7 +273,7 @@ impl DcBuilder {
 
         if let Some(features) = apps.custom.feature {
             for app_type in features {
-                let service = app_type.app.try_into_service(); //todo
+                let service = Service::try_from(&app_type.app)?;
                 services.insert(app_type.app.code.clone().to_owned(), Some(service));
                 named_volumes.extend(extract_named_volumes(app_type.app.clone()));
             }

From 3e01e8741207c96b4c2963de66365cf3368b8493 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sun, 7 Jan 2024 23:50:43 +0200
Subject: [PATCH 120/284] issue-auth TryInto<Port>

---
 src/helpers/stack/builder.rs | 32 +++++++++++++++++---------------
 1 file changed, 17 insertions(+), 15 deletions(-)

diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index 4acf9631..55314ce2 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -1,4 +1,5 @@
 use crate::forms::{stack, App, StackForm, Volume, Web};
+use crate::forms;
 use crate::helpers::stack::dctypes::{
     AdvancedVolumes, Compose, ComposeNetwork, ComposeNetworkSettingDetails, ComposeNetworks,
     ComposeVolume, Entrypoint, Environment, MapOrEmpty, NetworkSettings, Networks, Port, Ports,
@@ -57,23 +58,21 @@ impl TryInto<AdvancedVolumes> for Volume {
     }
 }
 
-impl TryInto<Port> for stack::Port {
+impl TryInto<Port> for &stack::Port {
     type Error = String;
     fn try_into(self) -> Result<Port, Self::Error> {
         let cp = self
             .container_port
-            .clone()
-            .unwrap_or("".to_string())
-            .parse::<u16>()
+            .as_ref()
+            .map_or(Ok(0u16), |s| s.parse::<u16>())
             .map_err(|err| "Could not parse port".to_string())?;
+
         let hp = self
             .host_port
-            .clone()
-            .unwrap_or("".to_string())
-            .parse::<u16>()
+            .as_ref()
+            .map_or(Ok(0u16), |s| s.parse::<u16>())
             .map_err(|err| "Could not parse port".to_string())?;
 
-        tracing::debug!("Port conversion result: cp: {:?} hp: {:?}", cp, hp);
         Ok(Port {
             target: cp,
             host_ip: None,
@@ -138,13 +137,16 @@ impl TryFrom<&App> for Service {
 
         let networks = Networks::try_from(&app.network).unwrap_or_default();
 
-        let ports: Vec<Port> = app //todo
-            .ports
-            .clone()
-            .unwrap_or_default()
-            .into_iter()
-            .map(|x| x.try_into().unwrap())
-            .collect();
+        let ports: Vec<Port> = match &app.ports {
+            Some(ports) => {
+                let mut collector = vec![];
+                for port in ports {
+                    collector.push(port.try_into()?);
+                }
+                collector
+            }
+            None => vec![]
+        };
 
         let volumes: Vec<AdvancedVolumes> = app //todo
             .volumes

From bddf56fc8e28e1f12964bfe8f82b78bb0bc61cc5 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Mon, 8 Jan 2024 21:47:11 +0200
Subject: [PATCH 121/284] issue_auth stack_app

---
 src/forms/mod.rs             |   4 ++
 src/forms/stack.rs           | 105 ++---------------------------
 src/forms/stack_app.rs       | 126 +++++++++++++++++++++++++++++++++++
 src/forms/stack_custom.rs    |  81 ++++++++++++++++++++++
 src/forms/stack_payload.rs   |   2 +-
 src/helpers/stack/builder.rs |  69 +------------------
 6 files changed, 218 insertions(+), 169 deletions(-)
 create mode 100644 src/forms/stack_app.rs
 create mode 100644 src/forms/stack_custom.rs

diff --git a/src/forms/mod.rs b/src/forms/mod.rs
index a1379dec..b7ef2dcf 100644
--- a/src/forms/mod.rs
+++ b/src/forms/mod.rs
@@ -3,8 +3,12 @@ pub mod stack;
 pub mod user;
 mod stack_payload;
 mod stack_form;
+mod stack_custom;
+mod stack_app;
 
 pub use rating::*;
 pub use stack::*;
 pub use stack_payload::*;
 pub use stack_form::*;
+pub use stack_custom::*;
+pub use stack_app::*;
diff --git a/src/forms/stack.rs b/src/forms/stack.rs
index a9fc3ccc..d62fa319 100644
--- a/src/forms/stack.rs
+++ b/src/forms/stack.rs
@@ -3,6 +3,7 @@ use serde_json::Value;
 use serde_valid::Validate;
 use std::collections::HashMap;
 use std::fmt;
+use crate::forms;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Role {
@@ -73,12 +74,6 @@ impl fmt::Display for DockerImage {
     }
 }
 
-impl AsRef<DockerImage> for App {
-    fn as_ref(&self) -> &DockerImage {
-        &self.docker_image
-    }
-}
-
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 #[serde(rename_all = "camelCase")]
 pub struct DomainList {}
@@ -91,103 +86,11 @@ pub struct Var {}
 pub struct Price {
     pub value: f64,
 }
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct Custom {
-    pub web: Vec<Web>,
-    pub feature: Option<Vec<Feature>>,
-    pub service: Option<Vec<Service>>,
-    #[validate(minimum = 0)]
-    #[validate(maximum = 10)]
-    pub servers_count: u32,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 50)]
-    pub custom_stack_code: String,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 255)]
-    pub project_git_url: Option<String>,
-    pub custom_stack_category: Option<Vec<String>>,
-    pub custom_stack_short_description: Option<String>,
-    pub custom_stack_description: Option<String>,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 255)]
-    pub project_name: String,
-    pub project_overview: Option<String>,
-    pub project_description: Option<String>,
-    #[serde(flatten)]
-    pub networks: ComposeNetworks, // all networks
-}
-
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Network {
     name: String,
 }
 
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct App {
-    #[serde(rename = "_etag")]
-    #[validate(min_length = 3)]
-    #[validate(max_length = 255)]
-    pub etag: Option<String>,
-    #[serde(rename = "_id")]
-    pub id: u32,
-    #[serde(rename = "_created")]
-    pub created: Option<String>,
-    #[serde(rename = "_updated")]
-    pub updated: Option<String>,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 50)]
-    pub name: String,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 50)]
-    pub code: String,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 50)]
-    #[serde(rename = "type")]
-    pub type_field: String,
-    #[serde(flatten)]
-    pub role: Role,
-    pub default: Option<bool>,
-    pub versions: Option<Vec<Version>>,
-    #[serde(flatten)]
-    pub docker_image: DockerImage,
-    #[serde(flatten)]
-    pub requirements: Requirements,
-    #[validate(minimum = 1)]
-    pub popularity: Option<u32>,
-    pub commercial: Option<bool>,
-    pub subscription: Option<Value>,
-    pub autodeploy: Option<bool>,
-    pub suggested: Option<bool>,
-    pub dependency: Option<Value>,
-    pub avoid_render: Option<bool>,
-    pub price: Option<Price>,
-    pub icon: Option<Icon>,
-    pub domain: Option<String>,
-    pub category_id: Option<u32>,
-    pub parent_app_id: Option<u32>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub descr: Option<String>,
-    pub full_description: Option<String>,
-    pub description: Option<String>,
-    pub plan_type: Option<String>,
-    pub ansible_var: Option<String>,
-    pub repo_dir: Option<String>,
-    pub url_app: Option<String>,
-    pub url_git: Option<String>,
-    pub restart: Option<String>,
-    pub volumes: Option<Vec<Volume>>,
-    #[serde(flatten)]
-    pub environment: Environment,
-    #[serde(flatten)]
-    pub network: ServiceNetworks,
-    // #[serde(flatten)]
-    // pub ports: Ports,
-    #[serde(rename(deserialize = "sharedPorts"))]
-    #[serde(rename(serialize = "shared_ports"))]
-    #[serde(alias = "shared_ports")]
-    pub ports: Option<Vec<Port>>,
-}
-
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct Environment {
     pub(crate) environment: Option<Vec<HashMap<String, String>>>,
@@ -217,7 +120,7 @@ pub struct ComposeNetworks {
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct Web {
     #[serde(flatten)]
-    pub app: App,
+    pub app: forms::App,
     pub custom: Option<bool>,
     pub main: bool,
 }
@@ -229,7 +132,7 @@ pub struct Feature {
     // #[serde(alias = "shared_ports")]
     // pub shared_ports: Option<Vec<Port>>,
     #[serde(flatten)]
-    pub app: App,
+    pub app: forms::App,
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
@@ -239,7 +142,7 @@ pub struct Service {
     // #[serde(alias = "shared_ports")]
     // pub shared_ports: Option<Vec<Port>>,
     #[serde(flatten)]
-    pub(crate) app: App,
+    pub(crate) app: forms::App,
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
diff --git a/src/forms/stack_app.rs b/src/forms/stack_app.rs
new file mode 100644
index 00000000..8ebdec38
--- /dev/null
+++ b/src/forms/stack_app.rs
@@ -0,0 +1,126 @@
+use crate::forms;
+use crate::helpers::stack::dctypes;
+use indexmap::IndexMap;
+use serde_json::Value;
+use serde::{Deserialize, Serialize};
+use serde_valid::Validate;
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct App {
+    #[serde(rename = "_etag")]
+    #[validate(min_length = 3)]
+    #[validate(max_length = 255)]
+    pub etag: Option<String>,
+    #[serde(rename = "_id")]
+    pub id: u32,
+    #[serde(rename = "_created")]
+    pub created: Option<String>,
+    #[serde(rename = "_updated")]
+    pub updated: Option<String>,
+    #[validate(min_length = 3)]
+    #[validate(max_length = 50)]
+    pub name: String,
+    #[validate(min_length = 3)]
+    #[validate(max_length = 50)]
+    pub code: String,
+    #[validate(min_length = 3)]
+    #[validate(max_length = 50)]
+    #[serde(rename = "type")]
+    pub type_field: String,
+    #[serde(flatten)]
+    pub role: forms::Role,
+    pub default: Option<bool>,
+    pub versions: Option<Vec<forms::Version>>,
+    #[serde(flatten)]
+    pub docker_image: forms::DockerImage,
+    #[serde(flatten)]
+    pub requirements: forms::Requirements,
+    #[validate(minimum = 1)]
+    pub popularity: Option<u32>,
+    pub commercial: Option<bool>,
+    pub subscription: Option<Value>,
+    pub autodeploy: Option<bool>,
+    pub suggested: Option<bool>,
+    pub dependency: Option<Value>,
+    pub avoid_render: Option<bool>,
+    pub price: Option<forms::Price>,
+    pub icon: Option<forms::Icon>,
+    pub domain: Option<String>,
+    pub category_id: Option<u32>,
+    pub parent_app_id: Option<u32>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub descr: Option<String>,
+    pub full_description: Option<String>,
+    pub description: Option<String>,
+    pub plan_type: Option<String>,
+    pub ansible_var: Option<String>,
+    pub repo_dir: Option<String>,
+    pub url_app: Option<String>,
+    pub url_git: Option<String>,
+    pub restart: Option<String>,
+    pub volumes: Option<Vec<forms::Volume>>,
+    #[serde(flatten)]
+    pub environment: forms::Environment,
+    #[serde(flatten)]
+    pub network: forms::ServiceNetworks,
+    // #[serde(flatten)]
+    // pub ports: Ports,
+    #[serde(rename(deserialize = "sharedPorts"))]
+    #[serde(rename(serialize = "shared_ports"))]
+    #[serde(alias = "shared_ports")]
+    pub ports: Option<Vec<forms::Port>>,
+}
+
+impl App {
+    pub fn named_volumes(&self) -> IndexMap<String, dctypes::MapOrEmpty<dctypes::ComposeVolume>> { //todo Result
+        let mut named_volumes = IndexMap::default();
+
+        let volumes = &self.volumes;
+        if volumes.is_none() {
+            return named_volumes;
+        }
+
+        let volumes = volumes
+            .clone() //todo remove it
+            .unwrap()
+            .into_iter()
+            .filter(|volume| is_named_docker_volume(volume.host_path.clone().unwrap().as_str()))
+            .map(|volume| {
+                let k = volume.host_path.clone().unwrap();
+                (
+                    k.clone(),
+                    dctypes::MapOrEmpty::Map(dctypes::ComposeVolume {
+                        driver: None,
+                        driver_opts: Default::default(),
+                        external: None,
+                        labels: Default::default(),
+                        name: Some(k.clone()),
+                    }),
+                    )
+            })
+        .collect::<IndexMap<String, dctypes::MapOrEmpty<dctypes::ComposeVolume>>>();
+
+        named_volumes.extend(volumes);
+        // tracing::debug!("Named volumes: {:?}", named_volumes);
+
+        named_volumes
+    }
+}
+
+impl AsRef<forms::DockerImage> for App {
+    fn as_ref(&self) -> &forms::DockerImage {
+        &self.docker_image
+    }
+}
+
+
+fn is_named_docker_volume(volume: &str) -> bool { //todo
+    // Docker named volumes typically don't contain special characters or slashes
+    // They are alphanumeric and may include underscores or hyphens
+    let is_alphanumeric = volume
+        .chars()
+        .all(|c| c.is_alphanumeric() || c == '_' || c == '-');
+    let does_not_contain_slash = !volume.contains('/');
+    is_alphanumeric && does_not_contain_slash
+}
+
diff --git a/src/forms/stack_custom.rs b/src/forms/stack_custom.rs
new file mode 100644
index 00000000..b6277a2f
--- /dev/null
+++ b/src/forms/stack_custom.rs
@@ -0,0 +1,81 @@
+use serde::{Deserialize, Serialize};
+use serde_json::Value;
+use serde_valid::Validate;
+use crate::forms;
+use indexmap::IndexMap;
+use crate::helpers::stack::dctypes;
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct Custom {
+    pub web: Vec<forms::Web>,
+    pub feature: Option<Vec<forms::Feature>>,
+    pub service: Option<Vec<forms::Service>>,
+    #[validate(minimum = 0)]
+    #[validate(maximum = 10)]
+    pub servers_count: u32,
+    #[validate(min_length = 3)]
+    #[validate(max_length = 50)]
+    pub custom_stack_code: String,
+    #[validate(min_length = 3)]
+    #[validate(max_length = 255)]
+    pub project_git_url: Option<String>,
+    pub custom_stack_category: Option<Vec<String>>,
+    pub custom_stack_short_description: Option<String>,
+    pub custom_stack_description: Option<String>,
+    #[validate(min_length = 3)]
+    #[validate(max_length = 255)]
+    pub project_name: String,
+    pub project_overview: Option<String>,
+    pub project_description: Option<String>,
+    #[serde(flatten)]
+    pub networks: forms::ComposeNetworks, // all networks
+}
+
+impl Custom {
+    pub fn services(&self) -> Result<IndexMap<String, Option<dctypes::Service>>, String> {
+        let mut services = IndexMap::new();
+
+        for app_type in &self.web {
+            let service = dctypes::Service::try_from(&app_type.app)?;
+            services.insert(app_type.app.code.clone().to_owned(), Some(service));
+        }
+
+        if let Some(srvs) = &self.service {
+            for app_type in srvs {
+                let service = dctypes::Service::try_from(&app_type.app)?;
+                services.insert(app_type.app.code.clone().to_owned(), Some(service));
+            }
+        }
+
+        if let Some(features) = &self.feature {
+            for app_type in features {
+                let service = dctypes::Service::try_from(&app_type.app)?;
+                services.insert(app_type.app.code.clone().to_owned(), Some(service));
+            }
+        }
+
+        Ok(services)
+    }
+
+    pub fn named_volumes(&self) -> Result<IndexMap<String, dctypes::MapOrEmpty<dctypes::ComposeVolume>>, String> {
+        let mut named_volumes = IndexMap::new();
+
+        for app_type in &self.web {
+            named_volumes.extend(app_type.app.named_volumes());
+        }
+
+        if let Some(srvs) = &self.service {
+            for app_type in srvs {
+                named_volumes.extend(app_type.app.named_volumes());
+            }
+        }
+
+        if let Some(features) = &self.feature {
+            for app_type in features {
+                named_volumes.extend(app_type.app.named_volumes());
+            }
+        }
+
+        Ok(named_volumes)
+    }
+}
diff --git a/src/forms/stack_payload.rs b/src/forms/stack_payload.rs
index 521642d5..e7a56cd0 100644
--- a/src/forms/stack_payload.rs
+++ b/src/forms/stack_payload.rs
@@ -35,7 +35,7 @@ pub struct StackPayload {
     pub stack_code: String,
     #[serde(rename = "selected_plan")]
     pub selected_plan: String,
-    pub custom: forms::stack::Custom,
+    pub custom: forms::Custom,
 }
 
 impl TryFrom<&models::Stack> for StackPayload {
diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index 55314ce2..4457cd2c 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -116,16 +116,6 @@ fn convert_shared_ports(ports: Option<Vec<stack::Port>>) -> Result<Vec<Port>, St
     Ok(_ports)
 }
 
-fn is_named_docker_volume(volume: &str) -> bool {
-    // Docker named volumes typically don't contain special characters or slashes
-    // They are alphanumeric and may include underscores or hyphens
-    let is_alphanumeric = volume
-        .chars()
-        .all(|c| c.is_alphanumeric() || c == '_' || c == '-');
-    let does_not_contain_slash = !volume.contains('/');
-    is_alphanumeric && does_not_contain_slash
-}
-
 impl TryFrom<&App> for Service {
     type Error = String;
 
@@ -207,39 +197,6 @@ impl Into<IndexMap<String, MapOrEmpty<NetworkSettings>>> for stack::ComposeNetwo
     }
 }
 
-pub fn extract_named_volumes(app: App) -> IndexMap<String, MapOrEmpty<ComposeVolume>> {
-    let mut named_volumes = IndexMap::default();
-
-    let volumes = app.volumes;
-    if volumes.is_none() {
-        return named_volumes;
-    }
-
-    let volumes = volumes
-        .unwrap()
-        .into_iter()
-        .filter(|volume| is_named_docker_volume(volume.host_path.clone().unwrap().as_str()))
-        .map(|volume| {
-            let k = volume.host_path.clone().unwrap();
-            (
-                k.clone(),
-                MapOrEmpty::Map(ComposeVolume {
-                    driver: None,
-                    driver_opts: Default::default(),
-                    external: None,
-                    labels: Default::default(),
-                    name: Some(k.clone()),
-                }),
-            )
-        })
-        .collect::<IndexMap<String, MapOrEmpty<ComposeVolume>>>();
-
-    named_volumes.extend(volumes);
-    // tracing::debug!("Named volumes: {:?}", named_volumes);
-
-    named_volumes
-}
-
 impl DcBuilder {
     pub fn new(stack: models::Stack) -> Self {
         DcBuilder {
@@ -256,30 +213,8 @@ impl DcBuilder {
         };
 
         let apps = StackForm::try_from(&self.stack)?; 
-        let mut services = IndexMap::new();
-        let mut named_volumes = IndexMap::default();
-
-        for app_type in &apps.custom.web {
-            let service = Service::try_from(&app_type.app)?;
-            services.insert(app_type.app.code.clone().to_owned(), Some(service));
-            named_volumes.extend(extract_named_volumes(app_type.app.clone()));
-        }
-
-        if let Some(srvs) = apps.custom.service {
-            for app_type in srvs {
-                let service = Service::try_from(&app_type.app)?;
-                services.insert(app_type.app.code.clone().to_owned(), Some(service));
-                named_volumes.extend(extract_named_volumes(app_type.app.clone()));
-            }
-        }
-
-        if let Some(features) = apps.custom.feature {
-            for app_type in features {
-                let service = Service::try_from(&app_type.app)?;
-                services.insert(app_type.app.code.clone().to_owned(), Some(service));
-                named_volumes.extend(extract_named_volumes(app_type.app.clone()));
-            }
-        }
+        let  services = apps.custom.services()?;
+        let  named_volumes = apps.custom.named_volumes()?;
 
         let networks = apps.custom.networks.clone();
         compose_content.networks = ComposeNetworks(networks.into());

From b8334e4b6818dce890553ada7790746b10a9705d Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Wed, 10 Jan 2024 17:53:32 +0200
Subject: [PATCH 122/284] issue-auth Volume::is_named_docker

---
 src/forms/stack.rs     | 13 +++++++++++
 src/forms/stack_app.rs | 52 +++++++++++++-----------------------------
 2 files changed, 29 insertions(+), 36 deletions(-)

diff --git a/src/forms/stack.rs b/src/forms/stack.rs
index d62fa319..5a1296c1 100644
--- a/src/forms/stack.rs
+++ b/src/forms/stack.rs
@@ -101,6 +101,19 @@ pub struct Volume {
     pub(crate) container_path: Option<String>,
 }
 
+impl Volume {
+    pub fn is_named_docker(&self) -> bool {
+        // Docker named volumes typically don't contain special characters or slashes
+        // They are alphanumeric and may include underscores or hyphens
+        self
+            .host_path
+            .as_ref()
+            .unwrap()
+            .chars()
+            .all(|c| c.is_alphanumeric() || c == '_' || c == '-')
+    }
+}
+
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct Volumes {
     volumes: Vec<Volume>,
diff --git a/src/forms/stack_app.rs b/src/forms/stack_app.rs
index 8ebdec38..5608d768 100644
--- a/src/forms/stack_app.rs
+++ b/src/forms/stack_app.rs
@@ -72,36 +72,28 @@ pub struct App {
 }
 
 impl App {
-    pub fn named_volumes(&self) -> IndexMap<String, dctypes::MapOrEmpty<dctypes::ComposeVolume>> { //todo Result
+    pub fn named_volumes(&self) -> IndexMap<String, dctypes::MapOrEmpty<dctypes::ComposeVolume>> { 
         let mut named_volumes = IndexMap::default();
 
-        let volumes = &self.volumes;
-        if volumes.is_none() {
+        if self.volumes.is_none() {
             return named_volumes;
         }
 
-        let volumes = volumes
-            .clone() //todo remove it
-            .unwrap()
-            .into_iter()
-            .filter(|volume| is_named_docker_volume(volume.host_path.clone().unwrap().as_str()))
-            .map(|volume| {
-                let k = volume.host_path.clone().unwrap();
-                (
-                    k.clone(),
-                    dctypes::MapOrEmpty::Map(dctypes::ComposeVolume {
-                        driver: None,
-                        driver_opts: Default::default(),
-                        external: None,
-                        labels: Default::default(),
-                        name: Some(k.clone()),
-                    }),
-                    )
-            })
-        .collect::<IndexMap<String, dctypes::MapOrEmpty<dctypes::ComposeVolume>>>();
+        for volume in self.volumes.as_ref().unwrap() {
+            if !volume.is_named_docker() {
+                continue;
+            }
 
-        named_volumes.extend(volumes);
-        // tracing::debug!("Named volumes: {:?}", named_volumes);
+            let k = volume.host_path.as_ref().unwrap().clone();
+            let v = dctypes::MapOrEmpty::Map(dctypes::ComposeVolume {
+                driver: None,
+                driver_opts: Default::default(),
+                external: None,
+                labels: Default::default(),
+                name: Some(k.clone()),
+            });
+            named_volumes.insert(k, v);
+        }
 
         named_volumes
     }
@@ -112,15 +104,3 @@ impl AsRef<forms::DockerImage> for App {
         &self.docker_image
     }
 }
-
-
-fn is_named_docker_volume(volume: &str) -> bool { //todo
-    // Docker named volumes typically don't contain special characters or slashes
-    // They are alphanumeric and may include underscores or hyphens
-    let is_alphanumeric = volume
-        .chars()
-        .all(|c| c.is_alphanumeric() || c == '_' || c == '-');
-    let does_not_contain_slash = !volume.contains('/');
-    is_alphanumeric && does_not_contain_slash
-}
-

From ea055ccfcd320c615972b57c46ae9a96003b0dc4 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Thu, 11 Jan 2024 18:02:00 +0200
Subject: [PATCH 123/284] issue-auth forms::stack

---
 src/forms/mod.rs                              |  9 -------
 src/forms/{stack_app.rs => stack/app.rs}      | 24 ++++++++---------
 .../{stack_custom.rs => stack/custom.rs}      |  8 +++---
 src/forms/{stack_form.rs => stack/form.rs}    | 12 ++++-----
 src/forms/{stack.rs => stack/mod.rs}          | 16 ++++++++---
 .../{stack_payload.rs => stack/payload.rs}    |  8 +++---
 src/helpers/stack/builder.rs                  | 27 +++++++++----------
 src/helpers/stack/dctypes.rs                  |  8 +-----
 src/middleware/client.rs                      |  2 +-
 src/middleware/trydirect.rs                   |  4 +--
 src/routes/client/update.rs                   |  1 -
 src/routes/rating/add.rs                      |  3 +--
 src/routes/rating/get.rs                      |  2 +-
 src/routes/stack/add.rs                       | 12 ++++-----
 src/routes/stack/deploy.rs                    |  3 +--
 src/routes/stack/update.rs                    |  8 +++---
 src/services/rating.rs                        |  2 --
 src/telemetry.rs                              |  2 +-
 18 files changed, 70 insertions(+), 81 deletions(-)
 rename src/forms/{stack_app.rs => stack/app.rs} (83%)
 rename src/forms/{stack_custom.rs => stack/custom.rs} (92%)
 rename src/forms/{stack_form.rs => stack/form.rs} (84%)
 rename src/forms/{stack.rs => stack/mod.rs} (96%)
 rename src/forms/{stack_payload.rs => stack/payload.rs} (87%)

diff --git a/src/forms/mod.rs b/src/forms/mod.rs
index b7ef2dcf..9647ea6e 100644
--- a/src/forms/mod.rs
+++ b/src/forms/mod.rs
@@ -1,14 +1,5 @@
 mod rating;
 pub mod stack;
 pub mod user;
-mod stack_payload;
-mod stack_form;
-mod stack_custom;
-mod stack_app;
 
 pub use rating::*;
-pub use stack::*;
-pub use stack_payload::*;
-pub use stack_form::*;
-pub use stack_custom::*;
-pub use stack_app::*;
diff --git a/src/forms/stack_app.rs b/src/forms/stack/app.rs
similarity index 83%
rename from src/forms/stack_app.rs
rename to src/forms/stack/app.rs
index 5608d768..71509262 100644
--- a/src/forms/stack_app.rs
+++ b/src/forms/stack/app.rs
@@ -28,13 +28,13 @@ pub struct App {
     #[serde(rename = "type")]
     pub type_field: String,
     #[serde(flatten)]
-    pub role: forms::Role,
+    pub role: forms::stack::Role,
     pub default: Option<bool>,
-    pub versions: Option<Vec<forms::Version>>,
+    pub versions: Option<Vec<forms::stack::Version>>,
     #[serde(flatten)]
-    pub docker_image: forms::DockerImage,
+    pub docker_image: forms::stack::DockerImage,
     #[serde(flatten)]
-    pub requirements: forms::Requirements,
+    pub requirements: forms::stack::Requirements,
     #[validate(minimum = 1)]
     pub popularity: Option<u32>,
     pub commercial: Option<bool>,
@@ -43,8 +43,8 @@ pub struct App {
     pub suggested: Option<bool>,
     pub dependency: Option<Value>,
     pub avoid_render: Option<bool>,
-    pub price: Option<forms::Price>,
-    pub icon: Option<forms::Icon>,
+    pub price: Option<forms::stack::Price>,
+    pub icon: Option<forms::stack::Icon>,
     pub domain: Option<String>,
     pub category_id: Option<u32>,
     pub parent_app_id: Option<u32>,
@@ -58,17 +58,17 @@ pub struct App {
     pub url_app: Option<String>,
     pub url_git: Option<String>,
     pub restart: Option<String>,
-    pub volumes: Option<Vec<forms::Volume>>,
+    pub volumes: Option<Vec<forms::stack::Volume>>,
     #[serde(flatten)]
-    pub environment: forms::Environment,
+    pub environment: forms::stack::Environment,
     #[serde(flatten)]
-    pub network: forms::ServiceNetworks,
+    pub network: forms::stack::ServiceNetworks,
     // #[serde(flatten)]
     // pub ports: Ports,
     #[serde(rename(deserialize = "sharedPorts"))]
     #[serde(rename(serialize = "shared_ports"))]
     #[serde(alias = "shared_ports")]
-    pub ports: Option<Vec<forms::Port>>,
+    pub ports: Option<Vec<forms::stack::Port>>,
 }
 
 impl App {
@@ -99,8 +99,8 @@ impl App {
     }
 }
 
-impl AsRef<forms::DockerImage> for App {
-    fn as_ref(&self) -> &forms::DockerImage {
+impl AsRef<forms::stack::DockerImage> for App {
+    fn as_ref(&self) -> &forms::stack::DockerImage {
         &self.docker_image
     }
 }
diff --git a/src/forms/stack_custom.rs b/src/forms/stack/custom.rs
similarity index 92%
rename from src/forms/stack_custom.rs
rename to src/forms/stack/custom.rs
index b6277a2f..34f5437b 100644
--- a/src/forms/stack_custom.rs
+++ b/src/forms/stack/custom.rs
@@ -7,9 +7,9 @@ use crate::helpers::stack::dctypes;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Custom {
-    pub web: Vec<forms::Web>,
-    pub feature: Option<Vec<forms::Feature>>,
-    pub service: Option<Vec<forms::Service>>,
+    pub web: Vec<forms::stack::Web>,
+    pub feature: Option<Vec<forms::stack::Feature>>,
+    pub service: Option<Vec<forms::stack::Service>>,
     #[validate(minimum = 0)]
     #[validate(maximum = 10)]
     pub servers_count: u32,
@@ -28,7 +28,7 @@ pub struct Custom {
     pub project_overview: Option<String>,
     pub project_description: Option<String>,
     #[serde(flatten)]
-    pub networks: forms::ComposeNetworks, // all networks
+    pub networks: forms::stack::ComposeNetworks, // all networks
 }
 
 impl Custom {
diff --git a/src/forms/stack_form.rs b/src/forms/stack/form.rs
similarity index 84%
rename from src/forms/stack_form.rs
rename to src/forms/stack/form.rs
index 989205af..4e11776f 100644
--- a/src/forms/stack_form.rs
+++ b/src/forms/stack/form.rs
@@ -7,12 +7,12 @@ use crate::models;
 use crate::forms;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct StackForm {
+pub struct Stack { 
     // #[validate(min_length=2)]
     // #[validate(max_length=255)]
     #[serde(rename = "commonDomain")]
     pub common_domain: Option<String>,
-    pub domain_list: Option<forms::DomainList>,
+    pub domain_list: Option<forms::stack::DomainList>,
     #[validate(min_length = 2)]
     #[validate(max_length = 255)]
     pub stack_code: Option<String>,
@@ -31,7 +31,7 @@ pub struct StackForm {
     #[validate(min_length = 3)]
     #[validate(max_length = 50)]
     pub ssl: String,
-    pub vars: Option<Vec<forms::Var>>,
+    pub vars: Option<Vec<forms::stack::Var>>,
     pub integrated_features: Option<Vec<Value>>,
     pub extended_features: Option<Vec<Value>>,
     pub subscriptions: Option<Vec<String>>,
@@ -49,13 +49,13 @@ pub struct StackForm {
     #[validate(min_length = 3)]
     #[validate(max_length = 50)]
     pub selected_plan: String,
-    pub custom: forms::Custom,
+    pub custom: forms::stack::Custom,
 }
 
-impl TryFrom<&models::Stack> for StackForm {
+impl TryFrom<&models::Stack> for Stack {
     type Error = String;
 
     fn try_from(stack: &models::Stack) -> Result<Self, Self::Error> {
-        serde_json::from_value::<StackForm>(stack.body.clone()).map_err(|err| format!("{:?}", err))
+        serde_json::from_value::<Stack>(stack.body.clone()).map_err(|err| format!("{:?}", err))
     }
 }
diff --git a/src/forms/stack.rs b/src/forms/stack/mod.rs
similarity index 96%
rename from src/forms/stack.rs
rename to src/forms/stack/mod.rs
index 5a1296c1..aadeddcc 100644
--- a/src/forms/stack.rs
+++ b/src/forms/stack/mod.rs
@@ -5,6 +5,16 @@ use std::collections::HashMap;
 use std::fmt;
 use crate::forms;
 
+mod app;
+mod custom;
+mod form;
+mod payload;
+
+pub use app::*;
+pub use custom::*;
+pub use form::*;
+pub use payload::*;
+
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Role {
     pub role: Option<Vec<String>>,
@@ -133,7 +143,7 @@ pub struct ComposeNetworks {
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct Web {
     #[serde(flatten)]
-    pub app: forms::App,
+    pub app: forms::stack::App,
     pub custom: Option<bool>,
     pub main: bool,
 }
@@ -145,7 +155,7 @@ pub struct Feature {
     // #[serde(alias = "shared_ports")]
     // pub shared_ports: Option<Vec<Port>>,
     #[serde(flatten)]
-    pub app: forms::App,
+    pub app: forms::stack::App,
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
@@ -155,7 +165,7 @@ pub struct Service {
     // #[serde(alias = "shared_ports")]
     // pub shared_ports: Option<Vec<Port>>,
     #[serde(flatten)]
-    pub(crate) app: forms::App,
+    pub(crate) app: forms::stack::App,
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
diff --git a/src/forms/stack_payload.rs b/src/forms/stack/payload.rs
similarity index 87%
rename from src/forms/stack_payload.rs
rename to src/forms/stack/payload.rs
index e7a56cd0..70be8410 100644
--- a/src/forms/stack_payload.rs
+++ b/src/forms/stack/payload.rs
@@ -7,7 +7,7 @@ use serde_valid::Validate;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 #[serde(rename_all = "snake_case")]
-pub struct StackPayload {
+pub struct Payload {
     pub(crate) id: Option<i32>,
     pub(crate) user_token: Option<String>,
     pub(crate) user_email: Option<String>,
@@ -35,14 +35,14 @@ pub struct StackPayload {
     pub stack_code: String,
     #[serde(rename = "selected_plan")]
     pub selected_plan: String,
-    pub custom: forms::Custom,
+    pub custom: forms::stack::Custom,
 }
 
-impl TryFrom<&models::Stack> for StackPayload {
+impl TryFrom<&models::Stack> for Payload {
     type Error = String;
 
     fn try_from(stack: &models::Stack) -> Result<Self, Self::Error> {
-        let mut stack_data = serde_json::from_value::<StackPayload>(stack.body.clone()).map_err(|err| {
+        let mut stack_data = serde_json::from_value::<Payload>(stack.body.clone()).map_err(|err| {
             format!("{:?}", err)
         })?;
 
diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index 4457cd2c..9068c9d0 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -1,4 +1,3 @@
-use crate::forms::{stack, App, StackForm, Volume, Web};
 use crate::forms;
 use crate::helpers::stack::dctypes::{
     AdvancedVolumes, Compose, ComposeNetwork, ComposeNetworkSettingDetails, ComposeNetworks,
@@ -36,7 +35,7 @@ pub struct DcBuilder {
     pub(crate) stack: models::Stack,
 }
 
-impl TryInto<AdvancedVolumes> for Volume {
+impl TryInto<AdvancedVolumes> for forms::stack::Volume {
     type Error = String;
     fn try_into(self) -> Result<AdvancedVolumes, Self::Error> {
         let source = self.host_path.clone();
@@ -58,20 +57,20 @@ impl TryInto<AdvancedVolumes> for Volume {
     }
 }
 
-impl TryInto<Port> for &stack::Port {
+impl TryInto<Port> for &forms::stack::Port {
     type Error = String;
     fn try_into(self) -> Result<Port, Self::Error> {
         let cp = self
             .container_port
             .as_ref()
             .map_or(Ok(0u16), |s| s.parse::<u16>())
-            .map_err(|err| "Could not parse port".to_string())?;
+            .map_err(|_| "Could not parse port".to_string())?;
 
         let hp = self
             .host_port
             .as_ref()
             .map_or(Ok(0u16), |s| s.parse::<u16>())
-            .map_err(|err| "Could not parse port".to_string())?;
+            .map_err(|_| "Could not parse port".to_string())?;
 
         Ok(Port {
             target: cp,
@@ -83,12 +82,12 @@ impl TryInto<Port> for &stack::Port {
     }
 }
 
-impl TryFrom<&stack::ServiceNetworks> for Networks {
+impl TryFrom<&forms::stack::ServiceNetworks> for Networks {
     type Error = ();
 
-    fn try_from(serviceNetworks: &stack::ServiceNetworks) -> Result<Networks, Self::Error> {
+    fn try_from(service_networks: &forms::stack::ServiceNetworks) -> Result<Networks, Self::Error> {
         let mut result = vec!["default_network".to_string()];
-        serviceNetworks.network.as_ref().map(|networks| {
+        service_networks.network.as_ref().map(|networks| {
             for n in networks {
                 result.push(n.to_string());
             }
@@ -98,13 +97,13 @@ impl TryFrom<&stack::ServiceNetworks> for Networks {
     }
 }
 
-fn convert_shared_ports(ports: Option<Vec<stack::Port>>) -> Result<Vec<Port>, String> {
+//todo
+fn convert_shared_ports(ports: Option<Vec<forms::stack::Port>>) -> Result<Vec<Port>, String> {
     tracing::debug!("convert shared ports {:?}", &ports);
     let mut _ports: Vec<Port> = vec![];
     match ports {
         Some(ports) => {
             tracing::debug!("Ports >>>> {:?}", ports);
-            for port in ports {}
         }
         None => {
             tracing::debug!("No ports defined by user");
@@ -116,10 +115,10 @@ fn convert_shared_ports(ports: Option<Vec<stack::Port>>) -> Result<Vec<Port>, St
     Ok(_ports)
 }
 
-impl TryFrom<&App> for Service {
+impl TryFrom<&forms::stack::App> for Service {
     type Error = String;
 
-    fn try_from(app: &App) -> Result<Self, Self::Error> {
+    fn try_from(app: &forms::stack::App) -> Result<Self, Self::Error> {
         let mut service = Service {
             image: Some(app.docker_image.to_string()),
             ..Default::default()
@@ -166,7 +165,7 @@ impl TryFrom<&App> for Service {
     }
 }
 
-impl Into<IndexMap<String, MapOrEmpty<NetworkSettings>>> for stack::ComposeNetworks {
+impl Into<IndexMap<String, MapOrEmpty<NetworkSettings>>> for forms::stack::ComposeNetworks {
     fn into(self) -> IndexMap<String, MapOrEmpty<NetworkSettings>> {
         // tracing::debug!("networks found {:?}", self.networks);
         let mut networks = vec!["default_network".to_string()];
@@ -212,7 +211,7 @@ impl DcBuilder {
             ..Default::default()
         };
 
-        let apps = StackForm::try_from(&self.stack)?; 
+        let apps = forms::stack::Stack::try_from(&self.stack)?; 
         let  services = apps.custom.services()?;
         let  named_volumes = apps.custom.named_volumes()?;
 
diff --git a/src/helpers/stack/dctypes.rs b/src/helpers/stack/dctypes.rs
index 931deab1..22383934 100644
--- a/src/helpers/stack/dctypes.rs
+++ b/src/helpers/stack/dctypes.rs
@@ -46,12 +46,6 @@ pub struct Compose {
     pub extensions: HashMap<Extension, Value>,
 }
 
-impl Compose {
-    pub fn new() -> Self {
-        Default::default()
-    }
-}
-
 #[derive(Builder, Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
 #[builder(setter(into), default)]
 pub struct Service {
@@ -839,4 +833,4 @@ impl<T> Serialize for MapOrEmpty<T>
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/middleware/client.rs b/src/middleware/client.rs
index 60bce270..a6915d55 100644
--- a/src/middleware/client.rs
+++ b/src/middleware/client.rs
@@ -77,7 +77,7 @@ where
             let header_hash: String = get_header(&req, "stacker-hash")?;
 
             let db_pool = req.app_data::<web::Data<Pool<Postgres>>>().unwrap().get_ref();
-            let mut client: Client = db_fetch_client(db_pool, client_id).await?;
+            let client: Client = db_fetch_client(db_pool, client_id).await?;
             if client.secret.is_none() {
                 return Err("client is not active".to_string());
             }
diff --git a/src/middleware/trydirect.rs b/src/middleware/trydirect.rs
index e40f89b1..62b47046 100644
--- a/src/middleware/trydirect.rs
+++ b/src/middleware/trydirect.rs
@@ -32,7 +32,7 @@ async fn fetch_user(auth_url: &str, token: &str) -> Result<models::User, String>
         .header(ACCEPT, "application/json")
         .send()
         .await
-        .map_err(|err| "no resp from auth server".to_string())?;
+        .map_err(|_err| "no resp from auth server".to_string())?;
 
     if !resp.status().is_success() {
         return Err("401 Unauthorized".to_string());
@@ -41,6 +41,6 @@ async fn fetch_user(auth_url: &str, token: &str) -> Result<models::User, String>
     resp
         .json::<UserForm>()
         .await
-        .map_err(|err| "can't parse the response body".to_string())?
+        .map_err(|_err| "can't parse the response body".to_string())?
         .try_into()
 }
diff --git a/src/routes/client/update.rs b/src/routes/client/update.rs
index a52af9b0..5f19de51 100644
--- a/src/routes/client/update.rs
+++ b/src/routes/client/update.rs
@@ -3,7 +3,6 @@ use crate::helpers::client;
 use crate::models;
 use crate::{configuration::Settings, helpers::JsonResponse};
 use actix_web::{put, web, Responder, Result};
-use futures::TryFutureExt;
 use sqlx::PgPool;
 use std::sync::Arc;
 use tracing::Instrument;
diff --git a/src/routes/rating/add.rs b/src/routes/rating/add.rs
index 48f03309..ea9657b2 100644
--- a/src/routes/rating/add.rs
+++ b/src/routes/rating/add.rs
@@ -6,7 +6,6 @@ use actix_web::{post, web, Responder, Result};
 use sqlx::PgPool;
 use tracing::Instrument;
 use std::sync::Arc;
-use futures::TryFutureExt;
 
 // workflow
 // add, update, list, get(user_id), ACL,
@@ -39,5 +38,5 @@ pub async fn add_handler(
     db::rating::insert(pg_pool.get_ref(), rating)
         .await
         .map(|rating| JsonResponse::build().set_item(rating).ok("success"))
-        .map_err(|err| JsonResponse::<models::Rating>::build().internal_server_error("Failed to insert"))
+        .map_err(|_err| JsonResponse::<models::Rating>::build().internal_server_error("Failed to insert"))
 }
diff --git a/src/routes/rating/get.rs b/src/routes/rating/get.rs
index 9081960c..c7591891 100644
--- a/src/routes/rating/get.rs
+++ b/src/routes/rating/get.rs
@@ -34,5 +34,5 @@ pub async fn list_handler(
     db::rating::fetch_all(pg_pool.get_ref())
         .await
         .map(|ratings| JsonResponse::build().set_list(ratings).ok("OK"))
-        .map_err(|err| JsonResponse::<models::Rating>::build().internal_server_error(""))
+        .map_err(|_err| JsonResponse::<models::Rating>::build().internal_server_error(""))
 }
diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs
index bcf281c7..5a850c09 100644
--- a/src/routes/stack/add.rs
+++ b/src/routes/stack/add.rs
@@ -27,8 +27,8 @@ pub async fn add(
 
     check_if_stack_exists(pg_pool.get_ref(), &stack_name).await?;
 
-    let body: Value = serde_json::to_value::<forms::StackForm>(form)
-        .or(serde_json::to_value::<forms::StackForm>(forms::StackForm::default()))
+    let body: Value = serde_json::to_value::<forms::stack::Stack>(form)
+        .or(serde_json::to_value::<forms::stack::Stack>(forms::stack::Stack::default()))
         .unwrap();
 
     let stack = models::Stack::new(user.id.clone(), stack_name, body);
@@ -53,17 +53,17 @@ async fn check_if_stack_exists(pg_pool: &PgPool, stack_name: &String) -> Result<
         })
 }
 
-async fn body_into_form(body: Bytes) -> Result<forms::StackForm, Error> {
+async fn body_into_form(body: Bytes) -> Result<forms::stack::Stack, Error> {
     let body_bytes = actix_web::body::to_bytes(body).await.unwrap();
     let body_str = str::from_utf8(&body_bytes)
-        .map_err(|err| JsonResponse::<forms::StackForm>::build().internal_server_error(err.to_string()))?;
+        .map_err(|err| JsonResponse::<forms::stack::Stack>::build().internal_server_error(err.to_string()))?;
     let deserializer = &mut serde_json::Deserializer::from_str(body_str);
     serde_path_to_error::deserialize(deserializer)
         .map_err(|err| {
             let msg = format!("{}:{:?}", err.path().to_string(), err);
-            JsonResponse::<forms::StackForm>::build().bad_request(msg)
+            JsonResponse::<forms::stack::Stack>::build().bad_request(msg)
         })
-        .and_then(|form: forms::StackForm| {
+        .and_then(|form: forms::stack::Stack| {
             if !form.validate().is_ok() {
                 let errors = form.validate().unwrap_err();
                 let err_msg = format!("Invalid data received {:?}", &errors.to_string());
diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs
index 005ac531..71d459ff 100644
--- a/src/routes/stack/deploy.rs
+++ b/src/routes/stack/deploy.rs
@@ -5,7 +5,6 @@ use crate::helpers::stack::builder::DcBuilder;
 use crate::helpers::{JsonResponse, MqManager};
 use crate::models;
 use actix_web::{post, web, web::Data, Responder, Result};
-use futures_lite::stream::StreamExt;
 use lapin::publisher_confirm::Confirmation;
 use sqlx::PgPool;
 use std::sync::Arc;
@@ -34,7 +33,7 @@ pub async fn add(
         JsonResponse::<models::Stack>::build().internal_server_error(err)
     })?;
 
-    let mut stack_data = forms::StackPayload::try_from(&dc.stack)
+    let mut stack_data = forms::stack::Payload::try_from(&dc.stack)
         .map_err(|err| JsonResponse::<models::Stack>::build().bad_request(err))?;
     stack_data.user_token = Some(user.id.clone());
     stack_data.user_email = Some(user.email.clone());
diff --git a/src/routes/stack/update.rs b/src/routes/stack/update.rs
index a22be0c2..316a1ba2 100644
--- a/src/routes/stack/update.rs
+++ b/src/routes/stack/update.rs
@@ -16,7 +16,7 @@ use uuid::Uuid;
 #[post("/{id}")]
 pub async fn update(
     path: web::Path<(i32,)>,
-    form: web::Json<forms::StackForm>,
+    form: web::Json<forms::stack::Stack>,
     user: web::ReqData<Arc<User>>,
     pg_pool: Data<PgPool>,
 ) -> Result<impl Responder> {
@@ -67,11 +67,11 @@ pub async fn update(
         return Err(JsonResponse::<models::Stack>::build().bad_request(errors.to_string()));
     }
 
-    let body: Value = match serde_json::to_value::<forms::StackForm>(form.into_inner()) {
+    let body: Value = match serde_json::to_value::<forms::stack::Stack>(form.into_inner()) {
         Ok(body) => body,
         Err(err) => {
             tracing::error!("Request_id {} error unwrap body {:?}", request_id, err);
-            serde_json::to_value::<forms::StackForm>(forms::StackForm::default()).unwrap()
+            serde_json::to_value::<forms::stack::Stack>(forms::stack::Stack::default()).unwrap()
         }
     };
 
@@ -93,7 +93,7 @@ pub async fn update(
     .instrument(query_span)
     .await
     {
-        Ok(record) => {
+        Ok(_record) => {
             tracing::info!(
                 "req_id: {} stack details have been saved to database",
                 request_id
diff --git a/src/services/rating.rs b/src/services/rating.rs
index 22f42024..82222211 100644
--- a/src/services/rating.rs
+++ b/src/services/rating.rs
@@ -1,6 +1,4 @@
 use crate::models::rating::Rating;
-use sqlx::PgPool;
-use reqwest::Url;
 use tracing::Instrument;
 use tracing_subscriber::fmt::format;
 
diff --git a/src/telemetry.rs b/src/telemetry.rs
index 724381a7..fb57df1e 100644
--- a/src/telemetry.rs
+++ b/src/telemetry.rs
@@ -1,4 +1,4 @@
-use tracing::subscriber::{self, set_global_default};
+use tracing::subscriber::set_global_default;
 use tracing::Subscriber;
 use tracing_bunyan_formatter::{BunyanFormattingLayer, JsonStorageLayer};
 use tracing_log::LogTracer;

From 11e315ad793bbead1745c066cd2504f702025a06 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Fri, 12 Jan 2024 18:37:51 +0200
Subject: [PATCH 124/284] issue-auth dctypes

---
 src/helpers/stack/builder.rs                  |  37 ----
 src/helpers/stack/dctypes/compose_file.rs     |  18 ++
 .../stack/{dctypes.rs => dctypes/mod.rs}      | 193 ++----------------
 src/helpers/stack/dctypes/port.rs             |  53 +++++
 src/helpers/stack/dctypes/published_port.rs   |   8 +
 src/helpers/stack/dctypes/service.rs          | 120 +++++++++++
 src/helpers/stack/dctypes/single_service.rs   |   8 +
 src/helpers/stack/dctypes/sys_ctls.rs         |  30 +++
 8 files changed, 254 insertions(+), 213 deletions(-)
 create mode 100644 src/helpers/stack/dctypes/compose_file.rs
 rename src/helpers/stack/{dctypes.rs => dctypes/mod.rs} (75%)
 create mode 100644 src/helpers/stack/dctypes/port.rs
 create mode 100644 src/helpers/stack/dctypes/published_port.rs
 create mode 100644 src/helpers/stack/dctypes/service.rs
 create mode 100644 src/helpers/stack/dctypes/single_service.rs
 create mode 100644 src/helpers/stack/dctypes/sys_ctls.rs

diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index 9068c9d0..334f5caa 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -16,18 +16,6 @@ impl Default for Config {
     }
 }
 
-impl Default for Port {
-    fn default() -> Self {
-        Port {
-            target: 80,
-            host_ip: None,
-            published: None,
-            protocol: None,
-            mode: None,
-        }
-    }
-}
-
 /// A builder for constructing docker compose.
 #[derive(Clone, Debug)]
 pub struct DcBuilder {
@@ -57,31 +45,6 @@ impl TryInto<AdvancedVolumes> for forms::stack::Volume {
     }
 }
 
-impl TryInto<Port> for &forms::stack::Port {
-    type Error = String;
-    fn try_into(self) -> Result<Port, Self::Error> {
-        let cp = self
-            .container_port
-            .as_ref()
-            .map_or(Ok(0u16), |s| s.parse::<u16>())
-            .map_err(|_| "Could not parse port".to_string())?;
-
-        let hp = self
-            .host_port
-            .as_ref()
-            .map_or(Ok(0u16), |s| s.parse::<u16>())
-            .map_err(|_| "Could not parse port".to_string())?;
-
-        Ok(Port {
-            target: cp,
-            host_ip: None,
-            published: Some(PublishedPort::Single(hp)),
-            protocol: None,
-            mode: None,
-        })
-    }
-}
-
 impl TryFrom<&forms::stack::ServiceNetworks> for Networks {
     type Error = ();
 
diff --git a/src/helpers/stack/dctypes/compose_file.rs b/src/helpers/stack/dctypes/compose_file.rs
new file mode 100644
index 00000000..1131f675
--- /dev/null
+++ b/src/helpers/stack/dctypes/compose_file.rs
@@ -0,0 +1,18 @@
+use serde::{Deserialize, Serialize};
+use crate::helpers::stack::dctypes;
+
+#[cfg(feature = "indexmap")]
+use indexmap::IndexMap;
+
+#[allow(clippy::large_enum_variant)]
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
+#[serde(untagged)]
+pub enum ComposeFile {
+    V2Plus(dctypes::Compose),
+    #[cfg(feature = "indexmap")]
+    V1(IndexMap<String, dctypes::Service>),
+    #[cfg(not(feature = "indexmap"))]
+    V1(HashMap<String, Service>),
+    Single(dctypes::SingleService),
+}
+
diff --git a/src/helpers/stack/dctypes.rs b/src/helpers/stack/dctypes/mod.rs
similarity index 75%
rename from src/helpers/stack/dctypes.rs
rename to src/helpers/stack/dctypes/mod.rs
index 22383934..507a696e 100644
--- a/src/helpers/stack/dctypes.rs
+++ b/src/helpers/stack/dctypes/mod.rs
@@ -1,3 +1,19 @@
+mod port;
+mod published_port;
+mod compose_file;
+mod single_service;
+mod service;
+mod sys_ctls;
+
+pub use port::*;
+pub use published_port::*;
+pub use compose_file::*;
+pub use single_service::*;
+pub use service::*;
+pub use sys_ctls::*;
+
+use crate::helpers::stack::dctypes;
+
 use derive_builder::*;
 #[cfg(feature = "indexmap")]
 use indexmap::IndexMap;
@@ -9,23 +25,6 @@ use std::convert::TryFrom;
 use std::fmt;
 use std::str::FromStr;
 
-#[allow(clippy::large_enum_variant)]
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
-#[serde(untagged)]
-pub enum ComposeFile {
-    V2Plus(Compose),
-    #[cfg(feature = "indexmap")]
-    V1(IndexMap<String, Service>),
-    #[cfg(not(feature = "indexmap"))]
-    V1(HashMap<String, Service>),
-    Single(SingleService),
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
-pub struct SingleService {
-    pub service: Service,
-}
-
 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
 pub struct Compose {
     #[serde(skip_serializing_if = "Option::is_none")]
@@ -46,119 +45,6 @@ pub struct Compose {
     pub extensions: HashMap<Extension, Value>,
 }
 
-#[derive(Builder, Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
-#[builder(setter(into), default)]
-pub struct Service {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub hostname: Option<String>,
-    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
-    pub privileged: bool,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub healthcheck: Option<Healthcheck>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub deploy: Option<Deploy>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub image: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub container_name: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none", rename = "build")]
-    pub build_: Option<BuildStep>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub pid: Option<String>,
-    #[serde(default, skip_serializing_if = "Ports::is_empty")]
-    pub ports: Ports,
-    #[serde(default, skip_serializing_if = "Environment::is_empty")]
-    pub environment: Environment,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub network_mode: Option<String>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub devices: Vec<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub restart: Option<String>,
-    #[serde(default, skip_serializing_if = "Labels::is_empty")]
-    pub labels: Labels,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub tmpfs: Option<Tmpfs>,
-    #[serde(default, skip_serializing_if = "Ulimits::is_empty")]
-    pub ulimits: Ulimits,
-    #[serde(default, skip_serializing_if = "Volumes::is_empty")]
-    pub volumes: Volumes,
-    #[serde(default, skip_serializing_if = "Networks::is_empty")]
-    pub networks: Networks,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub cap_add: Vec<String>,
-    #[serde(default, skip_serializing_if = "DependsOnOptions::is_empty")]
-    pub depends_on: DependsOnOptions,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub command: Option<Command>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub entrypoint: Option<Entrypoint>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub env_file: Option<EnvFile>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub stop_grace_period: Option<String>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub profiles: Vec<String>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub links: Vec<String>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub dns: Vec<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub ipc: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub net: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub stop_signal: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub user: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub working_dir: Option<String>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub expose: Vec<String>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub volumes_from: Vec<String>,
-    #[cfg(feature = "indexmap")]
-    #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
-    pub extends: IndexMap<String, String>,
-    #[cfg(not(feature = "indexmap"))]
-    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
-    pub extends: HashMap<String, String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub logging: Option<LoggingParameters>,
-    #[serde(default, skip_serializing_if = "is_zero")]
-    pub scale: i64,
-    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
-    pub init: bool,
-    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
-    pub stdin_open: bool,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub shm_size: Option<String>,
-    #[cfg(feature = "indexmap")]
-    #[serde(flatten, skip_serializing_if = "IndexMap::is_empty")]
-    pub extensions: IndexMap<Extension, Value>,
-    #[cfg(not(feature = "indexmap"))]
-    #[serde(flatten, skip_serializing_if = "HashMap::is_empty")]
-    pub extensions: HashMap<Extension, Value>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub extra_hosts: Vec<String>,
-    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
-    pub tty: bool,
-    #[serde(default, skip_serializing_if = "SysCtls::is_empty")]
-    pub sysctls: SysCtls,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub security_opt: Vec<String>,
-}
-
-impl Service {
-    pub fn image(&self) -> &str {
-        self.image.as_deref().unwrap_or_default()
-    }
-
-    pub fn network_mode(&self) -> &str {
-        self.network_mode.as_deref().unwrap_or_default()
-    }
-}
-
 #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
 #[serde(untagged)]
 pub enum EnvFile {
@@ -211,7 +97,7 @@ pub struct LoggingParameters {
 #[serde(untagged)]
 pub enum Ports {
     Short(Vec<String>),
-    Long(Vec<Port>),
+    Long(Vec<dctypes::Port>),
 }
 
 impl Default for Ports {
@@ -229,26 +115,6 @@ impl Ports {
     }
 }
 
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
-pub struct Port {
-    pub target: u16,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub host_ip: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub published: Option<PublishedPort>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub protocol: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub mode: Option<String>,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
-#[serde(untagged)]
-pub enum PublishedPort {
-    Single(u16),
-    Range(String),
-}
-
 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
 #[serde(untagged)]
 pub enum Environment {
@@ -457,31 +323,6 @@ pub struct AdvancedNetworkSettings {
     pub aliases: Vec<String>,
 }
 
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
-#[serde(untagged)]
-pub enum SysCtls {
-    List(Vec<String>),
-    #[cfg(feature = "indexmap")]
-    Map(IndexMap<String, Option<SingleValue>>),
-    #[cfg(not(feature = "indexmap"))]
-    Map(HashMap<String, Option<SingleValue>>),
-}
-
-impl Default for SysCtls {
-    fn default() -> Self {
-        Self::List(Vec::new())
-    }
-}
-
-impl SysCtls {
-    pub fn is_empty(&self) -> bool {
-        match self {
-            Self::List(v) => v.is_empty(),
-            Self::Map(m) => m.is_empty(),
-        }
-    }
-}
-
 #[cfg(feature = "indexmap")]
 #[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
 pub struct TopLevelVolumes(pub IndexMap<String, MapOrEmpty<ComposeVolume>>);
diff --git a/src/helpers/stack/dctypes/port.rs b/src/helpers/stack/dctypes/port.rs
new file mode 100644
index 00000000..25557581
--- /dev/null
+++ b/src/helpers/stack/dctypes/port.rs
@@ -0,0 +1,53 @@
+use serde::{Deserialize, Serialize};
+use crate::helpers::stack::dctypes;
+use crate::forms;
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
+pub struct Port {
+    pub target: u16,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub host_ip: Option<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub published: Option<dctypes::PublishedPort>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub protocol: Option<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub mode: Option<String>,
+}
+
+impl Default for Port {
+    fn default() -> Self {
+        Port {
+            target: 80,
+            host_ip: None,
+            published: None,
+            protocol: None,
+            mode: None,
+        }
+    }
+}
+
+impl TryInto<Port> for &forms::stack::Port {
+    type Error = String;
+    fn try_into(self) -> Result<Port, Self::Error> {
+        let cp = self
+            .container_port
+            .as_ref()
+            .map_or(Ok(0u16), |s| s.parse::<u16>())
+            .map_err(|_| "Could not parse port".to_string())?;
+
+        let hp = self
+            .host_port
+            .as_ref()
+            .map_or(Ok(0u16), |s| s.parse::<u16>())
+            .map_err(|_| "Could not parse port".to_string())?;
+
+        Ok(Port {
+            target: cp,
+            host_ip: None,
+            published: Some(dctypes::PublishedPort::Single(hp)),
+            protocol: None,
+            mode: None,
+        })
+    }
+}
diff --git a/src/helpers/stack/dctypes/published_port.rs b/src/helpers/stack/dctypes/published_port.rs
new file mode 100644
index 00000000..57a24ab3
--- /dev/null
+++ b/src/helpers/stack/dctypes/published_port.rs
@@ -0,0 +1,8 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
+#[serde(untagged)]
+pub enum PublishedPort {
+    Single(u16),
+    Range(String),
+}
diff --git a/src/helpers/stack/dctypes/service.rs b/src/helpers/stack/dctypes/service.rs
new file mode 100644
index 00000000..4cebefcf
--- /dev/null
+++ b/src/helpers/stack/dctypes/service.rs
@@ -0,0 +1,120 @@
+use serde::{Deserialize, Serialize};
+#[cfg(feature = "indexmap")]
+use indexmap::IndexMap;
+use crate::helpers::stack::dctypes;
+use serde_json::Value;
+use derive_builder::*;
+
+#[derive(Builder, Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
+#[builder(setter(into), default)]
+pub struct Service {
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub hostname: Option<String>,
+    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
+    pub privileged: bool,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub healthcheck: Option<dctypes::Healthcheck>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub deploy: Option<dctypes::Deploy>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub image: Option<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub container_name: Option<String>,
+    #[serde(skip_serializing_if = "Option::is_none", rename = "build")]
+    pub build_: Option<dctypes::BuildStep>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub pid: Option<String>,
+    #[serde(default, skip_serializing_if = "dctypes::Ports::is_empty")]
+    pub ports: dctypes::Ports,
+    #[serde(default, skip_serializing_if = "dctypes::Environment::is_empty")]
+    pub environment: dctypes::Environment,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub network_mode: Option<String>,
+    #[serde(default, skip_serializing_if = "Vec::is_empty")]
+    pub devices: Vec<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub restart: Option<String>,
+    #[serde(default, skip_serializing_if = "dctypes::Labels::is_empty")]
+    pub labels: dctypes::Labels,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub tmpfs: Option<dctypes::Tmpfs>,
+    #[serde(default, skip_serializing_if = "dctypes::Ulimits::is_empty")]
+    pub ulimits: dctypes::Ulimits,
+    #[serde(default, skip_serializing_if = "dctypes::Volumes::is_empty")]
+    pub volumes: dctypes::Volumes,
+    #[serde(default, skip_serializing_if = "dctypes::Networks::is_empty")]
+    pub networks: dctypes::Networks,
+    #[serde(default, skip_serializing_if = "Vec::is_empty")]
+    pub cap_add: Vec<String>,
+    #[serde(default, skip_serializing_if = "dctypes::DependsOnOptions::is_empty")]
+    pub depends_on: dctypes::DependsOnOptions,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub command: Option<dctypes::Command>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub entrypoint: Option<dctypes::Entrypoint>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub env_file: Option<dctypes::EnvFile>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub stop_grace_period: Option<String>,
+    #[serde(default, skip_serializing_if = "Vec::is_empty")]
+    pub profiles: Vec<String>,
+    #[serde(default, skip_serializing_if = "Vec::is_empty")]
+    pub links: Vec<String>,
+    #[serde(default, skip_serializing_if = "Vec::is_empty")]
+    pub dns: Vec<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub ipc: Option<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub net: Option<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub stop_signal: Option<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub user: Option<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub working_dir: Option<String>,
+    #[serde(default, skip_serializing_if = "Vec::is_empty")]
+    pub expose: Vec<String>,
+    #[serde(default, skip_serializing_if = "Vec::is_empty")]
+    pub volumes_from: Vec<String>,
+    #[cfg(feature = "indexmap")]
+    #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
+    pub extends: IndexMap<String, String>,
+    #[cfg(not(feature = "indexmap"))]
+    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
+    pub extends: HashMap<String, String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub logging: Option<dctypes::LoggingParameters>,
+    #[serde(default, skip_serializing_if = "dctypes::is_zero")]
+    pub scale: i64,
+    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
+    pub init: bool,
+    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
+    pub stdin_open: bool,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub shm_size: Option<String>,
+    #[cfg(feature = "indexmap")]
+    #[serde(flatten, skip_serializing_if = "IndexMap::is_empty")]
+    pub extensions: IndexMap<dctypes::Extension, Value>,
+    #[cfg(not(feature = "indexmap"))]
+    #[serde(flatten, skip_serializing_if = "HashMap::is_empty")]
+    pub extensions: HashMap<Extension, Value>,
+    #[serde(default, skip_serializing_if = "Vec::is_empty")]
+    pub extra_hosts: Vec<String>,
+    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
+    pub tty: bool,
+    #[serde(default, skip_serializing_if = "dctypes::SysCtls::is_empty")]
+    pub sysctls: dctypes::SysCtls,
+    #[serde(default, skip_serializing_if = "Vec::is_empty")]
+    pub security_opt: Vec<String>,
+}
+
+impl Service {
+    pub fn image(&self) -> &str {
+        self.image.as_deref().unwrap_or_default()
+    }
+
+    pub fn network_mode(&self) -> &str {
+        self.network_mode.as_deref().unwrap_or_default()
+    }
+}
+
diff --git a/src/helpers/stack/dctypes/single_service.rs b/src/helpers/stack/dctypes/single_service.rs
new file mode 100644
index 00000000..489a3063
--- /dev/null
+++ b/src/helpers/stack/dctypes/single_service.rs
@@ -0,0 +1,8 @@
+use serde::{Deserialize, Serialize};
+use crate::helpers::stack::dctypes;
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
+pub struct SingleService {
+    pub service: dctypes::Service,
+}
+
diff --git a/src/helpers/stack/dctypes/sys_ctls.rs b/src/helpers/stack/dctypes/sys_ctls.rs
new file mode 100644
index 00000000..45d0a7c3
--- /dev/null
+++ b/src/helpers/stack/dctypes/sys_ctls.rs
@@ -0,0 +1,30 @@
+use serde::{Deserialize, Serialize};
+use crate::helpers::stack::dctypes;
+#[cfg(feature = "indexmap")]
+use indexmap::IndexMap;
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
+#[serde(untagged)]
+pub enum SysCtls {
+    List(Vec<String>),
+    #[cfg(feature = "indexmap")]
+    Map(IndexMap<String, Option<dctypes::SingleValue>>),
+    #[cfg(not(feature = "indexmap"))]
+    Map(HashMap<String, Option<dctypes::SingleValue>>),
+}
+
+impl Default for SysCtls {
+    fn default() -> Self {
+        Self::List(Vec::new())
+    }
+}
+
+impl SysCtls {
+    pub fn is_empty(&self) -> bool {
+        match self {
+            Self::List(v) => v.is_empty(),
+            Self::Map(m) => m.is_empty(),
+        }
+    }
+}
+

From 58ec7c916a92722c610d6962778ff6574f04eab0 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 13 Jan 2024 09:59:54 +0200
Subject: [PATCH 125/284] issue-auth dctypes

---
 src/forms/stack/mod.rs                        |   1 -
 src/helpers/stack/dctypes/compose.rs          |  25 +++
 .../stack/dctypes/depends_condition.rs        |   6 +
 .../stack/dctypes/depends_on_options.rs       |  30 +++
 src/helpers/stack/dctypes/env_file.rs         |   9 +
 src/helpers/stack/dctypes/environment.rs      |  30 +++
 src/helpers/stack/dctypes/extension.rs        |  28 +++
 .../stack/dctypes/extension_parse_error.rs    |  15 ++
 .../stack/dctypes/logging_parameters.rs       |  15 ++
 src/helpers/stack/dctypes/mod.rs              | 188 ++----------------
 src/helpers/stack/dctypes/ports.rs            |  25 +++
 src/helpers/stack/dctypes/services.rs         |  19 ++
 src/models/rating.rs                          |   2 +-
 13 files changed, 223 insertions(+), 170 deletions(-)
 create mode 100644 src/helpers/stack/dctypes/compose.rs
 create mode 100644 src/helpers/stack/dctypes/depends_condition.rs
 create mode 100644 src/helpers/stack/dctypes/depends_on_options.rs
 create mode 100644 src/helpers/stack/dctypes/env_file.rs
 create mode 100644 src/helpers/stack/dctypes/environment.rs
 create mode 100644 src/helpers/stack/dctypes/extension.rs
 create mode 100644 src/helpers/stack/dctypes/extension_parse_error.rs
 create mode 100644 src/helpers/stack/dctypes/logging_parameters.rs
 create mode 100644 src/helpers/stack/dctypes/ports.rs
 create mode 100644 src/helpers/stack/dctypes/services.rs

diff --git a/src/forms/stack/mod.rs b/src/forms/stack/mod.rs
index aadeddcc..01a95891 100644
--- a/src/forms/stack/mod.rs
+++ b/src/forms/stack/mod.rs
@@ -1,5 +1,4 @@
 use serde::{Deserialize, Serialize};
-use serde_json::Value;
 use serde_valid::Validate;
 use std::collections::HashMap;
 use std::fmt;
diff --git a/src/helpers/stack/dctypes/compose.rs b/src/helpers/stack/dctypes/compose.rs
new file mode 100644
index 00000000..82372d6a
--- /dev/null
+++ b/src/helpers/stack/dctypes/compose.rs
@@ -0,0 +1,25 @@
+use serde::{Deserialize, Serialize};
+use crate::helpers::stack::dctypes;
+#[cfg(feature = "indexmap")]
+use indexmap::IndexMap;
+use serde_yaml::Value;
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
+pub struct Compose {
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub version: Option<String>,
+    #[serde(default, skip_serializing_if = "dctypes::Services::is_empty")]
+    pub services: dctypes::Services,
+    #[serde(default, skip_serializing_if = "dctypes::TopLevelVolumes::is_empty")]
+    pub volumes: dctypes::TopLevelVolumes,
+    #[serde(default, skip_serializing_if = "dctypes::ComposeNetworks::is_empty")]
+    pub networks: dctypes::ComposeNetworks,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub service: Option<dctypes::Service>,
+    #[cfg(feature = "indexmap")]
+    #[serde(flatten, skip_serializing_if = "IndexMap::is_empty")]
+    pub extensions: IndexMap<dctypes::Extension, Value>,
+    #[cfg(not(feature = "indexmap"))]
+    #[serde(flatten, skip_serializing_if = "HashMap::is_empty")]
+    pub extensions: HashMap<dctypes::Extension, Value>,
+}
diff --git a/src/helpers/stack/dctypes/depends_condition.rs b/src/helpers/stack/dctypes/depends_condition.rs
new file mode 100644
index 00000000..863c7b5c
--- /dev/null
+++ b/src/helpers/stack/dctypes/depends_condition.rs
@@ -0,0 +1,6 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
+pub struct DependsCondition {
+    pub condition: String,
+}
diff --git a/src/helpers/stack/dctypes/depends_on_options.rs b/src/helpers/stack/dctypes/depends_on_options.rs
new file mode 100644
index 00000000..6e12300b
--- /dev/null
+++ b/src/helpers/stack/dctypes/depends_on_options.rs
@@ -0,0 +1,30 @@
+use serde::{Deserialize, Serialize};
+#[cfg(feature = "indexmap")]
+use indexmap::IndexMap;
+use crate::helpers::stack::dctypes;
+
+#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
+#[serde(untagged)]
+pub enum DependsOnOptions {
+    Simple(Vec<String>),
+    #[cfg(feature = "indexmap")]
+    Conditional(IndexMap<String, dctypes::DependsCondition>),
+    #[cfg(not(feature = "indexmap"))]
+    Conditional(HashMap<String, dctypes::DependsCondition>),
+}
+
+impl Default for DependsOnOptions {
+    fn default() -> Self {
+        Self::Simple(Vec::new())
+    }
+}
+
+impl DependsOnOptions {
+    pub fn is_empty(&self) -> bool {
+        match self {
+            Self::Simple(v) => v.is_empty(),
+            Self::Conditional(m) => m.is_empty(),
+        }
+    }
+}
+
diff --git a/src/helpers/stack/dctypes/env_file.rs b/src/helpers/stack/dctypes/env_file.rs
new file mode 100644
index 00000000..643d6b99
--- /dev/null
+++ b/src/helpers/stack/dctypes/env_file.rs
@@ -0,0 +1,9 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
+#[serde(untagged)]
+pub enum EnvFile {
+    Simple(String),
+    List(Vec<String>),
+}
+
diff --git a/src/helpers/stack/dctypes/environment.rs b/src/helpers/stack/dctypes/environment.rs
new file mode 100644
index 00000000..3afafed6
--- /dev/null
+++ b/src/helpers/stack/dctypes/environment.rs
@@ -0,0 +1,30 @@
+use serde::{Deserialize, Serialize};
+use serde_yaml::Value;
+#[cfg(feature = "indexmap")]
+use indexmap::IndexMap;
+use crate::helpers::stack::dctypes;
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
+#[serde(untagged)]
+pub enum Environment {
+    List(Vec<String>),
+    #[cfg(feature = "indexmap")]
+    KvPair(IndexMap<String, Option<dctypes::SingleValue>>),
+    #[cfg(not(feature = "indexmap"))]
+    KvPair(HashMap<String, Option<dctypes::SingleValue>>),
+}
+
+impl Default for Environment {
+    fn default() -> Self {
+        Self::List(Vec::new())
+    }
+}
+
+impl Environment {
+    pub fn is_empty(&self) -> bool {
+        match self {
+            Self::List(v) => v.is_empty(),
+            Self::KvPair(m) => m.is_empty(),
+        }
+    }
+}
diff --git a/src/helpers/stack/dctypes/extension.rs b/src/helpers/stack/dctypes/extension.rs
new file mode 100644
index 00000000..f70871c5
--- /dev/null
+++ b/src/helpers/stack/dctypes/extension.rs
@@ -0,0 +1,28 @@
+use serde::{Deserialize, Serialize};
+use crate::helpers::stack::dctypes;
+use std::str::FromStr;
+
+#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default, Ord, PartialOrd)]
+#[serde(try_from = "String")]
+pub struct Extension(String);
+
+impl FromStr for Extension {
+    type Err = dctypes::ExtensionParseError;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        let owned = s.to_owned();
+        Extension::try_from(owned)
+    }
+}
+
+impl TryFrom<String> for Extension {
+    type Error = dctypes::ExtensionParseError;
+
+    fn try_from(s: String) -> Result<Self, Self::Error> {
+        if s.starts_with("x-") {
+            Ok(Self(s))
+        } else {
+            Err(dctypes::ExtensionParseError(s))
+        }
+    }
+}
diff --git a/src/helpers/stack/dctypes/extension_parse_error.rs b/src/helpers/stack/dctypes/extension_parse_error.rs
new file mode 100644
index 00000000..9fcec518
--- /dev/null
+++ b/src/helpers/stack/dctypes/extension_parse_error.rs
@@ -0,0 +1,15 @@
+use std::fmt;
+
+/// The result of a failed TryFrom<String> conversion for [`Extension`]
+///
+/// Contains the string that was being converted
+#[derive(Clone, Debug, Eq, PartialEq, Hash)]
+pub struct ExtensionParseError(pub String);
+
+impl fmt::Display for ExtensionParseError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "unknown attribute {:?}, extensions must start with 'x-' (see https://docs.docker.com/compose/compose-file/#extension)", self.0)
+    }
+}
+
+impl std::error::Error for ExtensionParseError {}
diff --git a/src/helpers/stack/dctypes/logging_parameters.rs b/src/helpers/stack/dctypes/logging_parameters.rs
new file mode 100644
index 00000000..bae66fcb
--- /dev/null
+++ b/src/helpers/stack/dctypes/logging_parameters.rs
@@ -0,0 +1,15 @@
+use serde::{Deserialize, Serialize};
+#[cfg(feature = "indexmap")]
+use indexmap::IndexMap;
+use crate::helpers::stack::dctypes;
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
+pub struct LoggingParameters {
+    pub driver: String,
+    #[cfg(feature = "indexmap")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub options: Option<IndexMap<String, dctypes::SingleValue>>,
+    #[cfg(not(feature = "indexmap"))]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub options: Option<HashMap<String, dctypes::SingleValue>>,
+}
diff --git a/src/helpers/stack/dctypes/mod.rs b/src/helpers/stack/dctypes/mod.rs
index 507a696e..efde67a3 100644
--- a/src/helpers/stack/dctypes/mod.rs
+++ b/src/helpers/stack/dctypes/mod.rs
@@ -4,6 +4,16 @@ mod compose_file;
 mod single_service;
 mod service;
 mod sys_ctls;
+mod compose;
+mod env_file;
+mod depends_on_options;
+mod depends_condition;
+mod logging_parameters;
+mod ports;
+mod environment;
+mod extension;
+mod extension_parse_error;
+mod services;
 
 pub use port::*;
 pub use published_port::*;
@@ -11,6 +21,16 @@ pub use compose_file::*;
 pub use single_service::*;
 pub use service::*;
 pub use sys_ctls::*;
+pub use compose::*;
+pub use env_file::*;
+pub use depends_on_options::*;
+pub use depends_condition::*;
+pub use logging_parameters::*;
+pub use ports::*;
+pub use environment::*;
+pub use extension::*;
+pub use extension_parse_error::*;
+pub use services::*;
 
 use crate::helpers::stack::dctypes;
 
@@ -23,174 +43,6 @@ use serde_yaml::Value;
 use std::collections::HashMap;
 use std::convert::TryFrom;
 use std::fmt;
-use std::str::FromStr;
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
-pub struct Compose {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub version: Option<String>,
-    #[serde(default, skip_serializing_if = "Services::is_empty")]
-    pub services: Services,
-    #[serde(default, skip_serializing_if = "TopLevelVolumes::is_empty")]
-    pub volumes: TopLevelVolumes,
-    #[serde(default, skip_serializing_if = "ComposeNetworks::is_empty")]
-    pub networks: ComposeNetworks,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub service: Option<Service>,
-    #[cfg(feature = "indexmap")]
-    #[serde(flatten, skip_serializing_if = "IndexMap::is_empty")]
-    pub extensions: IndexMap<Extension, Value>,
-    #[cfg(not(feature = "indexmap"))]
-    #[serde(flatten, skip_serializing_if = "HashMap::is_empty")]
-    pub extensions: HashMap<Extension, Value>,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(untagged)]
-pub enum EnvFile {
-    Simple(String),
-    List(Vec<String>),
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
-#[serde(untagged)]
-pub enum DependsOnOptions {
-    Simple(Vec<String>),
-    #[cfg(feature = "indexmap")]
-    Conditional(IndexMap<String, DependsCondition>),
-    #[cfg(not(feature = "indexmap"))]
-    Conditional(HashMap<String, DependsCondition>),
-}
-
-impl Default for DependsOnOptions {
-    fn default() -> Self {
-        Self::Simple(Vec::new())
-    }
-}
-
-impl DependsOnOptions {
-    pub fn is_empty(&self) -> bool {
-        match self {
-            Self::Simple(v) => v.is_empty(),
-            Self::Conditional(m) => m.is_empty(),
-        }
-    }
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-pub struct DependsCondition {
-    pub condition: String,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
-pub struct LoggingParameters {
-    pub driver: String,
-    #[cfg(feature = "indexmap")]
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub options: Option<IndexMap<String, SingleValue>>,
-    #[cfg(not(feature = "indexmap"))]
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub options: Option<HashMap<String, SingleValue>>,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
-#[serde(untagged)]
-pub enum Ports {
-    Short(Vec<String>),
-    Long(Vec<dctypes::Port>),
-}
-
-impl Default for Ports {
-    fn default() -> Self {
-        Self::Short(Vec::default())
-    }
-}
-
-impl Ports {
-    pub fn is_empty(&self) -> bool {
-        match self {
-            Self::Short(v) => v.is_empty(),
-            Self::Long(v) => v.is_empty(),
-        }
-    }
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
-#[serde(untagged)]
-pub enum Environment {
-    List(Vec<String>),
-    #[cfg(feature = "indexmap")]
-    KvPair(IndexMap<String, Option<SingleValue>>),
-    #[cfg(not(feature = "indexmap"))]
-    KvPair(HashMap<String, Option<SingleValue>>),
-}
-
-impl Default for Environment {
-    fn default() -> Self {
-        Self::List(Vec::new())
-    }
-}
-
-impl Environment {
-    pub fn is_empty(&self) -> bool {
-        match self {
-            Self::List(v) => v.is_empty(),
-            Self::KvPair(m) => m.is_empty(),
-        }
-    }
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default, Ord, PartialOrd)]
-#[serde(try_from = "String")]
-pub struct Extension(String);
-
-impl FromStr for Extension {
-    type Err = ExtensionParseError;
-
-    fn from_str(s: &str) -> Result<Self, Self::Err> {
-        let owned = s.to_owned();
-        Extension::try_from(owned)
-    }
-}
-
-impl TryFrom<String> for Extension {
-    type Error = ExtensionParseError;
-
-    fn try_from(s: String) -> Result<Self, Self::Error> {
-        if s.starts_with("x-") {
-            Ok(Self(s))
-        } else {
-            Err(ExtensionParseError(s))
-        }
-    }
-}
-
-/// The result of a failed TryFrom<String> conversion for [`Extension`]
-///
-/// Contains the string that was being converted
-#[derive(Clone, Debug, Eq, PartialEq, Hash)]
-pub struct ExtensionParseError(pub String);
-
-impl fmt::Display for ExtensionParseError {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "unknown attribute {:?}, extensions must start with 'x-' (see https://docs.docker.com/compose/compose-file/#extension)", self.0)
-    }
-}
-
-impl std::error::Error for ExtensionParseError {}
-
-#[cfg(feature = "indexmap")]
-#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
-pub struct Services(pub IndexMap<String, Option<Service>>);
-#[cfg(not(feature = "indexmap"))]
-#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
-pub struct Services(pub HashMap<String, Option<Service>>);
-
-impl Services {
-    pub fn is_empty(&self) -> bool {
-        self.0.is_empty()
-    }
-}
 
 #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
 #[serde(untagged)]
diff --git a/src/helpers/stack/dctypes/ports.rs b/src/helpers/stack/dctypes/ports.rs
new file mode 100644
index 00000000..bdcc6a81
--- /dev/null
+++ b/src/helpers/stack/dctypes/ports.rs
@@ -0,0 +1,25 @@
+use serde::{Deserialize, Serialize};
+use crate::helpers::stack::dctypes;
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
+#[serde(untagged)]
+pub enum Ports {
+    Short(Vec<String>),
+    Long(Vec<dctypes::Port>),
+}
+
+impl Default for Ports {
+    fn default() -> Self {
+        Self::Short(Vec::default())
+    }
+}
+
+impl Ports {
+    pub fn is_empty(&self) -> bool {
+        match self {
+            Self::Short(v) => v.is_empty(),
+            Self::Long(v) => v.is_empty(),
+        }
+    }
+}
+
diff --git a/src/helpers/stack/dctypes/services.rs b/src/helpers/stack/dctypes/services.rs
new file mode 100644
index 00000000..b41ff10d
--- /dev/null
+++ b/src/helpers/stack/dctypes/services.rs
@@ -0,0 +1,19 @@
+use serde::{Deserialize, Serialize};
+#[cfg(feature = "indexmap")]
+use indexmap::IndexMap;
+#[cfg(not(feature = "indexmap"))]
+use std::collections::HashMap;
+use crate::helpers::stack::dctypes;
+
+#[cfg(feature = "indexmap")]
+#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
+pub struct Services(pub IndexMap<String, Option<dctypes::Service>>);
+#[cfg(not(feature = "indexmap"))]
+#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
+pub struct Services(pub HashMap<String, Option<dctypes::Service>>);
+
+impl Services {
+    pub fn is_empty(&self) -> bool {
+        self.0.is_empty()
+    }
+}
diff --git a/src/models/rating.rs b/src/models/rating.rs
index e1522dea..55a4444b 100644
--- a/src/models/rating.rs
+++ b/src/models/rating.rs
@@ -1,5 +1,5 @@
 use chrono::{DateTime, Utc};
-use serde::{Deserialize, Serialize};
+use serde::{Serialize};
 use crate::models;
 
 #[derive(Debug, Serialize, Default)]

From 69f8fe888a0f448bfa0834fbad0977b5f7cbea8c Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Sat, 13 Jan 2024 12:16:00 +0200
Subject: [PATCH 126/284] incomplete dockerhub image validation

---
 src/forms/stack.rs                 |  55 ++++++++++--
 src/helpers/dockerhub.rs           | 124 +++++++++++++++++++++++++
 src/helpers/mod.rs                 |   6 +-
 src/routes/rating/add.rs           |   9 +-
 src/routes/rating/get.rs           |   2 +-
 src/routes/stack/add.rs            |  12 +--
 src/routes/stack/service.rs        |   0
 src/routes/stack/update.rs         |   1 +
 tests/custom-stack-payload-12.json |   1 +
 tests/dockerhub.rs                 | 139 +++++++++++++++++++++++++++++
 tests/model_user_stack.rs          | 113 +++++++++++++++++++----
 11 files changed, 424 insertions(+), 38 deletions(-)
 create mode 100644 src/helpers/dockerhub.rs
 create mode 100644 src/routes/stack/service.rs
 create mode 100644 tests/custom-stack-payload-12.json
 create mode 100644 tests/dockerhub.rs

diff --git a/src/forms/stack.rs b/src/forms/stack.rs
index 00742c68..0459e662 100644
--- a/src/forms/stack.rs
+++ b/src/forms/stack.rs
@@ -3,6 +3,7 @@ use serde_json::Value;
 use serde_valid::Validate;
 use std::collections::HashMap;
 use std::fmt;
+use crate::helpers::{login, docker_image_exists};
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Role {
@@ -33,38 +34,74 @@ pub struct Port {
     pub container_port: Option<String>,
 }
 
+async fn validate_dockerhub_image(
+    dockerhub_user: &Option<String>,
+    dockerhub_password: &Option<String>,
+    dockerhub_name: &Option<String>) -> Result<(), serde_valid::validation::Error> {
 
+    let result = login(
+        dockerhub_user.clone().unwrap_or("".to_string()).as_ref(),
+        dockerhub_password.clone().unwrap_or("".to_string()).as_ref()
+    ).await;
+
+    let exists: bool = match result.unwrap().token {
+        Some(tok) => {
+            let exists = docker_image_exists(
+                dockerhub_user.clone().unwrap().as_str(),
+                dockerhub_name.clone().unwrap().as_str(), tok).await;
+            match exists {
+                Ok(_) => true,
+                Err(err) => {
+                    println!("{:?}", err);
+                    false
+                }
+            }
+        }
+        _ => false,
+    };
+    if !exists {
+        return Err(serde_valid::validation::Error::Custom(
+            "Could not access docker image repository, please check credentials.".to_owned(),
+        ));
+    }
+    Ok(())
+}
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+#[validate(custom(|s| validate_dockerhub_image(dockerhub_user,dockerhub_name, dockerhub_password)))]
 pub struct DockerImage {
     #[validate(min_length = 3)]
     #[validate(max_length = 50)]
+    #[validate(pattern = r"^[a-z0-9]+([-_.][a-z0-9]+)*$")]
     pub dockerhub_user: Option<String>,
     #[validate(min_length = 3)]
     #[validate(max_length = 50)]
+    #[validate(pattern = r"^[a-z0-9]+([-_.][a-z0-9]+)*$")]
     pub dockerhub_name: Option<String>,
     #[validate(min_length = 3)]
     #[validate(max_length = 100)]
+    #[validate(pattern = r"^(?:(?=[^:\/]{1,253})(?!-)[a-zA-Z0-9-]{1,63}(?<!-)(?:\.(?!-)[a-zA-Z0-9-]{1,63}(?<!-))*(?::[0-9]{1,5})?/)?((?![._-])(?:[a-z0-9._-]*)(?<![._-])(?:/(?![._-])[a-z0-9._-]*(?<![._-]))*)(?::(?![.-])[a-zA-Z0-9_.-]{1,128})?$
+")]
     pub dockerhub_image: Option<String>,
+    pub dockerhub_password: Option<String>,
 }
 
 impl fmt::Display for DockerImage {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let tag = "latest";
+        let dh_image = self.dockerhub_image.as_ref().map(String::as_str).unwrap_or("");
+        let dh_nmspc = self.dockerhub_user.as_ref().map(String::as_str).unwrap_or("");
+        let dh_name = self.dockerhub_name.as_ref().map(String::as_str).unwrap_or("");
 
-        let dim = self.dockerhub_image.clone().unwrap_or("".to_string());
         write!(
             f,
-            "{}/{}:{}",
-            self.dockerhub_user
-                .clone()
-                .unwrap_or("trydirect".to_string())
-                .clone(),
-            self.dockerhub_name.clone().unwrap_or(dim),
-            tag
+            "{}{}{}",
+            if !dh_nmspc.is_empty() { format!("{}/", dh_nmspc) } else { String::new() },
+            if !dh_name.is_empty() { dh_name } else { dh_image },
+            if !dh_name.contains(":") && dh_image.is_empty() { ":latest".to_string() } else { String::new() },
         )
     }
 }
 
+
 impl AsRef<DockerImage> for App {
     fn as_ref(&self) -> &DockerImage {
         &self.docker_image
diff --git a/src/helpers/dockerhub.rs b/src/helpers/dockerhub.rs
new file mode 100644
index 00000000..e766b16a
--- /dev/null
+++ b/src/helpers/dockerhub.rs
@@ -0,0 +1,124 @@
+use serde_derive::{Deserialize, Serialize};
+use serde_valid::Validate;
+// use tracing_subscriber::fmt::format;
+use serde_json::Value;
+
+// #[tokio::main]
+// async fn main() -> Result<(), String> {
+    // tokio::select! {
+    //     Ok(true) = docker_image_exist()  => {
+    //         println!("first branch. image exists.");
+    //     }
+    //     Ok(true) = docker_image_exist()  => {
+    //         println!("second branch. image exists.");
+    //     }
+    //     Ok(true) = docker_image_exist()  => {
+    //         println!("third branch. image exists.");
+    //     }
+    //     else => {
+    //         println!("else branch. image does not exists");
+    //     }
+    // };
+//     Ok(())
+// }
+
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct DockerHubToken {
+    pub token: Option<String>
+}
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct DockerHubCreds<'a>  {
+    username: &'a str,
+    password: &'a str
+}
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+struct Image {
+    architecture: String,
+    digest: Option<String>,
+    features: Option<String>,
+    last_pulled: Option<String>,
+    last_pushed: Option<String>,
+    os: String,
+    os_features: Option<String>,
+    os_version: Option<String>,
+    size: i64,
+    status: String,
+    variant: Option<String>,
+}
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+struct Tag {
+    content_type: String,
+    creator: i64,
+    digest: Option<String>,
+    full_size: i64,
+    id: i64,
+    images: Vec<Image>,
+    last_updated: String,
+    last_updater: i64,
+    last_updater_username: String,
+    media_type: String,
+    name: String,
+    repository: i64,
+    tag_last_pulled: Option<String>,
+    tag_last_pushed: Option<String>,
+    tag_status: String,
+    v2: bool
+}
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+struct TagResult {
+    count: i64,
+    next: Value,
+    previous: Value,
+    results: Vec<Tag>
+}
+pub async fn login(username: &str, password: &str) -> Result<DockerHubToken, String> {
+    let endpoint = "https://hub.docker.com/v2/users/login";
+    let creds = DockerHubCreds { username, password };
+    let token = reqwest::Client::new()
+        .post(endpoint)
+        .json(&creds)
+        .send()
+        .await
+        .map_err(|err| format!("{}", err))?
+        .json::<DockerHubToken>()
+        .await
+        .map_err(|err| format!("{}", err))?;
+
+    Ok(token)
+}
+
+
+pub async fn docker_image_exists(user: &str, repo: &str, token: String) -> Result<bool, String> {
+    // get repo images
+    let tags_url = format!("https://hub.docker.com/v2/namespaces/{}/repositories/{}/tags",
+        user, repo);
+
+    let tags = reqwest::Client::new()
+        .get(tags_url)
+        .header("Accept", "application/json")
+        .bearer_auth(token)
+        .send()
+        .await
+        .map_err(|err| format!("{}", err))?
+        .json::<TagResult>()
+        // .json::<serde_json::Value>()
+        .await
+        .map_err(|err| format!("{}", err))?;
+
+    // println!("tags count: {:?}", tags.count);
+
+    if tags.count > 0 {
+        // let's find at least one active tag
+        let active = tags.results
+            .into_iter()
+            .any(|tag| tag.tag_status.contains("active") );
+        // println!("is active {:?}", active);
+        Ok(active)
+    } else {
+        Err(String::from("There was no active images found in this repository"))
+    }
+}
diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs
index 53978753..3fe765fb 100644
--- a/src/helpers/mod.rs
+++ b/src/helpers/mod.rs
@@ -3,4 +3,8 @@ pub(crate) mod json;
 pub(crate) mod stack;
 pub use json::*;
 pub(crate) mod compressor;
-pub use compressor::*;
\ No newline at end of file
+pub mod dockerhub;
+
+pub use compressor::*;
+
+pub use dockerhub::*;
diff --git a/src/routes/rating/add.rs b/src/routes/rating/add.rs
index 48f03309..e191bd13 100644
--- a/src/routes/rating/add.rs
+++ b/src/routes/rating/add.rs
@@ -22,13 +22,15 @@ pub async fn add_handler(
 ) -> Result<impl Responder> {
     let _product = db::product::fetch_by_obj(pg_pool.get_ref(), form.obj_id)
         .await
-        .map_err(|msg| JsonResponse::<models::Rating>::build().internal_server_error(msg))?
+        .map_err(|_msg| JsonResponse::<models::Rating>::build().internal_server_error(_msg))?
         .ok_or_else(|| JsonResponse::<models::Rating>::build().not_found("not found"))?
         ; 
 
-    let rating = db::rating::fetch_by_obj_and_user_and_category(pg_pool.get_ref(), form.obj_id, user.id.clone(), form.category)
+    let rating = db::rating::fetch_by_obj_and_user_and_category(
+        pg_pool.get_ref(), form.obj_id, user.id.clone(), form.category)
         .await
         .map_err(|err| JsonResponse::<models::Rating>::build().internal_server_error(err))?;
+
     if rating.is_some() {
         return Err(JsonResponse::<models::Rating>::build().bad_request("already rated"));
     }
@@ -39,5 +41,6 @@ pub async fn add_handler(
     db::rating::insert(pg_pool.get_ref(), rating)
         .await
         .map(|rating| JsonResponse::build().set_item(rating).ok("success"))
-        .map_err(|err| JsonResponse::<models::Rating>::build().internal_server_error("Failed to insert"))
+        .map_err(|_err| JsonResponse::<models::Rating>::build()
+            .internal_server_error("Failed to insert"))
 }
diff --git a/src/routes/rating/get.rs b/src/routes/rating/get.rs
index 824180fb..2edfa7e5 100644
--- a/src/routes/rating/get.rs
+++ b/src/routes/rating/get.rs
@@ -36,5 +36,5 @@ pub async fn list_handler(path: web::Path<()>, pool: web::Data<PgPool>) -> Resul
     db::rating::fetch_all(pool.get_ref())
         .await
         .map(|ratings| JsonResponse::build().set_list(ratings).ok("OK"))
-        .map_err(|err| JsonResponse::<models::Rating>::build().internal_server_error(""))
+        .map_err(|_err| JsonResponse::<models::Rating>::build().internal_server_error(""))
 }
diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs
index ef38ac5f..b5767346 100644
--- a/src/routes/stack/add.rs
+++ b/src/routes/stack/add.rs
@@ -25,7 +25,7 @@ pub async fn add(
     let form = body_into_form(body).await?;
     let stack_name = form.custom.custom_stack_code.clone();
 
-    check_if_stack_exists(pool.get_ref(), &stack_name).await?;
+    stack_exists(pool.get_ref(), &stack_name).await?;
 
     let body: Value = serde_json::to_value::<StackForm>(form)
         .or(serde_json::to_value::<StackForm>(StackForm::default()))
@@ -40,7 +40,8 @@ pub async fn add(
         })
 }
 
-async fn check_if_stack_exists(pool: &PgPool, stack_name: &String) -> Result<(), Error> {
+
+async fn stack_exists(pool: &PgPool, stack_name: &String) -> Result<(), Error> {
     db::stack::fetch_one_by_name(pool, stack_name)
         .await
         .map_err(|_| {
@@ -56,14 +57,15 @@ async fn check_if_stack_exists(pool: &PgPool, stack_name: &String) -> Result<(),
 async fn body_into_form(body: Bytes) -> Result<StackForm, Error> {
     let body_bytes = actix_web::body::to_bytes(body).await.unwrap();
     let body_str = str::from_utf8(&body_bytes)
-        .map_err(|err| JsonResponse::<StackForm>::build().internal_server_error(err.to_string()))?;
+        .map_err(|_err| JsonResponse::<StackForm>::build().internal_server_error(_err.to_string()))?;
     let deserializer = &mut serde_json::Deserializer::from_str(body_str);
     serde_path_to_error::deserialize(deserializer)
-        .map_err(|err| {
-            let msg = format!("{}:{:?}", err.path().to_string(), err);
+        .map_err(|_err| {
+            let msg = format!("{}:{:?}", _err.path().to_string(), _err);
             JsonResponse::<StackForm>::build().bad_request(msg)
         })
         .and_then(|form: StackForm| {
+
             if !form.validate().is_ok() {
                 let errors = form.validate().unwrap_err();
                 let err_msg = format!("Invalid data received {:?}", &errors.to_string());
diff --git a/src/routes/stack/service.rs b/src/routes/stack/service.rs
new file mode 100644
index 00000000..e69de29b
diff --git a/src/routes/stack/update.rs b/src/routes/stack/update.rs
index a418b532..ed4d3b71 100644
--- a/src/routes/stack/update.rs
+++ b/src/routes/stack/update.rs
@@ -72,6 +72,7 @@ pub async fn update(
         let errors = form.validate().unwrap_err();
         let err_msg = format!("Invalid data received {:?}", &errors.to_string());
         tracing::debug!(err_msg);
+
         return Err(JsonResponse::<models::Stack>::build().bad_request(errors.to_string()));
     }
 
diff --git a/tests/custom-stack-payload-12.json b/tests/custom-stack-payload-12.json
new file mode 100644
index 00000000..d405ff42
--- /dev/null
+++ b/tests/custom-stack-payload-12.json
@@ -0,0 +1 @@
+{"commonDomain":"justates222tee.com","domainList":{},"region":"fsn1","zone":null,"server":"cx21","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":["portainer_ce_feature"],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":true,"disk_type":"pd-standart","cloud_token":"***********","provider":"htz","stack_code":"custom-stack-2","selected_plan":"plan-individual-monthly","custom":{"web":[{"_etag":null,"_id":180,"_created":"2021-12-17T08:11:40.875486","_updated":"2023-11-21T17:37:24.594545","name":"Airflow","code":"airflow","role":["airflow"],"type":"web","default":true,"popularity":null,"descr":null,"ports":{"private":["31"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":150,"height":150,"image":"7f41d873-2de5-4c6e-a037-42eeff572db6.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\">Airflow description </p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.0","ram_size":null,"disk_size":null,"dockerhub_image":"airflow","form":null,"category":[null],"group":[],"versions":[{"_id":425,"name":"Airflow","version":"2.7.3","update_status":"published","tag":"latest"},{"_id":426,"name":"Airflow","version":"2.7.1-unstable","update_status":"published","tag":"stable"}],"links":[],"domain":"latestports.com","sharedPorts":[{"host_port":"5000","container_port":"5000"}],"main":true,"version":{"_id":426,"name":"Airflow","version":"2.7.1-unstable","update_status":"published","tag":"stable"},"environment":[{"ENV_VAR1":"ENV_VAR1_VALUE"}, {"ENV_VAR2":"ENV_VAR1_VALUe2"}],"network":["testnetwork"],"restart":"always","volumes":[{"host_path":"./airflow/host/path/","container_path":"/airflow/container/path"}, {"host_path":"./airflow/host/path2/","container_path":"/airflow/container/path2"}, {"host_path":"airflow","container_path":"/airflow/named/volume/path"}]},{"category":[{"name":"integrated_features"}],"name":"CustomNginx","code":"customnginx","domain":"customnginx.latestports.com","sharedPorts":[{"host_port":"80","container_port":"8080"},{"host_port":"443","container_port":"8443"}],"versions":[],"custom":true,"type":"web","main":false,"_id":0,"dockerhub_user":"trydirect","dockerhub_name":"nginx","disk_size":"0.9Gb","ram_size":"1.3Gb","cpu":"0.3","volumes":[{"host_path":"./host/path","container_path":"/container/path"}, {"host_path":  "namedvolume", "container_path": "/container/path/to/"}],"environment":[{"CUSTOM_NGINX_ENV_VAR1":"CUSTOM_NGINX_ENV_VAR1_VALUE"},{"CUSTOM_NGINX_VAR2": "CUSTOM_NGINX_VAR_VALUE2"}, {"CUSTOM_NGINX_ENV_VAR3":"CUSTOM_NGINX_ENV_VAR1_VALUE3"},{"CUSTOM_NGINX_VAR4": "CUSTOM_NGINX_VAR_VALUE4"}],"network":["testnetwork"],"restart":"always"}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\"><strong style=\"font-weight:bold\">Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)</strong></p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.6","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","form":null,"category":[{"name":"integrated_features"}],"group":[],"versions":[{"_id":456,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"}],"links":[],"version":{"_id":456,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"},"sharedPorts":[{"host_port":"9000","container_port":"9000"}],"environment":[{"PORTAINER_ENV_VAR1":"PORTAINER_ENV_VAR1_VALUE"}],"volumes":[{"host_path":"/portainer/test/path","container_path":"/portainer/test/path"}],"network":["testnetwork"],"restart":"always"}],"service":[{"_etag":null,"_id":24,"_created":"2020-06-19T13:07:24.228389","_updated":"2023-11-23T10:18:28.095813","name":"PostgreSQL","code":"postgres","role":["postgres"],"type":"service","default":null,"popularity":null,"descr":null,"ports":{"private":["5432"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":576,"height":594,"image":"fd23f54c-e250-4228-8d56-7e5d93ffb925.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":null,"plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.0","ram_size":null,"disk_size":null,"dockerhub_image":"postgres","form":null,"category":[null],"group":[{"_etag":null,"_id":12,"_created":null,"_updated":null,"name":"Database","code":"database"}],"versions":[{"_id":458,"name":"15","version":"15","update_status":"published","tag":"15"},{"_id":288,"name":"10.22","version":"10.22","update_status":"published","tag":"10.22"},{"_id":303,"name":"13.8","version":"13.8","update_status":"published","tag":"13.8"},{"_id":266,"name":"11","version":"11","update_status":"published","tag":"11"},{"_id":267,"name":"12.12","version":"12.12","update_status":"published","tag":"12.12"},{"_id":38,"name":"14.5","version":"14.5","update_status":"published","tag":"14.5"},{"_id":596,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"}],"links":[],"domain":"","sharedPorts":[{"host_port":"5432","container_port":"5432"}],"main":false,"version":{"_id":596,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"},"environment":[{"POSTGRES_ENV_VAR1":"POSTGRES_ENV_VAR1_VALUE"},{"POSTGRES_ENV_VAR2":"POSTGRES_ENV_VAR2_VALUE"}],"network":["testnetwork"],"restart":"always","volumes":[{"host_path":"./pg.conf","container_path":"/etc/postgres/pg.conf"}]}],"servers_count":3,"project_name":"DockerHubCheck1","custom_stack_code":"hubcheck","project_git_url":"https://github.com/latestports","custom_stack_category":[],"networks":["testnetwork","testnetwork2"]}}
\ No newline at end of file
diff --git a/tests/dockerhub.rs b/tests/dockerhub.rs
new file mode 100644
index 00000000..e13fe555
--- /dev/null
+++ b/tests/dockerhub.rs
@@ -0,0 +1,139 @@
+// use std::fs;
+// use std::collections::HashMap;
+use std::env;
+mod common;
+use stacker::helpers::dockerhub::{login, docker_image_exists};
+
+const DOCKER_USERNAME: &str = "trydirect";
+const DOCKER_PASSWORD: &str = "dckr_pat_NyQoqqA2VFtBtCuZRp8YcjbuFkU";
+
+//  Unit Test
+
+// #[test]
+// fn test_deserialize_user_stack_web() {
+//
+//     let body_str = fs::read_to_string("./tests/web-item.json").unwrap();
+//     // let form:serde_json::Value = serde_json::from_str(&body_str).unwrap();
+//     let form:App = serde_json::from_str(&body_str).unwrap();
+//     println!("{:?}", form);
+//     // {
+//     //     Ok(f) => {
+//     //         f
+//     //     }
+//     //     Err(_err) => {
+//     //         let msg = format!("Invalid data. {:?}", _err);
+//     //         return JsonResponse::<StackForm>::build().bad_request(msg);
+//     //     }
+//     // };
+//     //
+//     // assert_eq!(result, 12);
+// }
+// #[test]
+// fn test_deserialize_user_stack() {
+//
+//     let body_str = fs::read_to_string("./tests/custom-stack-payload-11.json").unwrap();
+//     let form = serde_json::from_str::<StackForm>(&body_str).unwrap();
+//     println!("{:?}", form);
+//     // @todo assert required data
+//
+//     // {
+//     //     Ok(f) => {
+//     //         f
+//     //     }
+//     //     Err(_err) => {
+//     //         let msg = format!("Invalid data. {:?}", _err);
+//     //         return JsonResponse::<StackForm>::build().bad_request(msg);
+//     //     }
+//     // };
+//     //
+//     // assert_eq!(result, 12);
+//
+//     // let form:Environment = serde_json::from_str(&body_str).unwrap();
+//     // let form:Vec<HashMap<String, String>> = serde_json::from_str(&body_str).unwrap();
+//     // println!("{:?}", form);
+// }
+
+#[tokio::test]
+async fn test_docker_hub_login() {
+
+    common::spawn_app().await; // server
+    // let username = env::var("TEST_DOCKER_USERNAME")
+    //     .expect("username environment variable is not set");
+    //
+    // let password= env::var("TEST_DOCKER_PASSWORD")
+    //     .expect("password environment variable is not set");
+    let result = login(
+        DOCKER_USERNAME.as_ref(),
+        DOCKER_PASSWORD.as_ref()
+    ).await;
+    let token = result.unwrap().token;
+
+    let exists: bool = match token {
+        Some(tok) => {
+            let exists = docker_image_exists(DOCKER_USERNAME, "nginx", tok).await;
+            match exists {
+                Ok(_) => true,
+                Err(err) => {
+                    println!("{:?}", err);
+                    false
+                }
+            }
+        }
+        _ => false,
+    };
+
+    assert_eq!(exists, true);
+}
+
+#[tokio::test]
+async fn test_docker_private_exists() {
+
+    common::spawn_app().await; // server
+    let result = login(
+        DOCKER_USERNAME.as_ref(), DOCKER_PASSWORD.as_ref()
+    ).await;
+    let token = result.unwrap().token;
+
+    let exists: bool = match token {
+        Some(tok) => {
+            let exists = docker_image_exists(DOCKER_USERNAME, "nginx-waf", tok).await;
+            match exists {
+                Ok(_) => true,
+                Err(err) => {
+                    println!("{:?}", err);
+                    false
+                }
+            }
+        }
+        _ => false,
+    };
+
+    assert_eq!(exists, true);
+}
+
+#[tokio::test]
+async fn test_docker_non_existent_repo() {
+
+    common::spawn_app().await; // server
+    let result= login(
+        DOCKER_USERNAME.as_ref(), DOCKER_PASSWORD.as_ref()
+    ).await;
+    let token = result.unwrap().token;
+
+    // println!("{:?}", token);
+    let exists: bool = match token {
+        Some(tok) => {
+            let exists = docker_image_exists(DOCKER_USERNAME, "nonexistent", tok).await;
+            match exists {
+                Ok(_) => true,
+                Err(err) => {
+                    println!("{:?}", err);
+                    false
+                }
+            }
+        }
+        _ => false,
+    };
+
+    assert_eq!(exists, false);
+}
diff --git a/tests/model_user_stack.rs b/tests/model_user_stack.rs
index e8ee9b6f..57e5f160 100644
--- a/tests/model_user_stack.rs
+++ b/tests/model_user_stack.rs
@@ -1,29 +1,30 @@
 use stacker::forms::stack::StackForm;
+use stacker::forms::stack::DockerImage;
 use stacker::forms::stack::App;
 use std::fs;
 use std::collections::HashMap;
 
 //  Unit Test
 
-#[test]
-fn test_deserialize_user_stack_web() {
-
-    let body_str = fs::read_to_string("./tests/web-item.json").unwrap();
-    // let form:serde_json::Value = serde_json::from_str(&body_str).unwrap();
-    let form:App = serde_json::from_str(&body_str).unwrap();
-    println!("{:?}", form);
-    // {
-    //     Ok(f) => {
-    //         f
-    //     }
-    //     Err(_err) => {
-    //         let msg = format!("Invalid data. {:?}", _err);
-    //         return JsonResponse::<StackForm>::build().bad_request(msg);
-    //     }
-    // };
-    //
-    // assert_eq!(result, 12);
-}
+// #[test]
+// fn test_deserialize_user_stack_web() {
+//
+//     let body_str = fs::read_to_string("./tests/web-item.json").unwrap();
+//     // let form:serde_json::Value = serde_json::from_str(&body_str).unwrap();
+//     let form:App = serde_json::from_str(&body_str).unwrap();
+//     println!("{:?}", form);
+//     // {
+//     //     Ok(f) => {
+//     //         f
+//     //     }
+//     //     Err(_err) => {
+//     //         let msg = format!("Invalid data. {:?}", _err);
+//     //         return JsonResponse::<StackForm>::build().bad_request(msg);
+//     //     }
+//     // };
+//     //
+//     // assert_eq!(result, 12);
+// }
 #[test]
 fn test_deserialize_user_stack() {
 
@@ -60,3 +61,77 @@ fn test_deserialize_user_stack() {
     // let form:Vec<HashMap<String, String>> = serde_json::from_str(&body_str).unwrap();
     // println!("{:?}", form);
 }
+
+#[test]
+fn test_docker_image_only_name_other_empty() {
+    let docker_image = DockerImage {
+        dockerhub_user: Some("".to_string()),
+        dockerhub_name: Some("mysql".to_string()),
+        dockerhub_image: Some("".to_string(),)
+    };
+    let output = docker_image.to_string();
+    assert_eq!(String::from("mysql:latest"), output);
+}
+
+#[test]
+fn test_docker_image_only_name_other_none() {
+    let docker_image = DockerImage {
+        dockerhub_user: None,
+        dockerhub_name: Some("mysql".to_string()),
+        dockerhub_image: None
+    };
+    let output = docker_image.to_string();
+    assert_eq!(String::from("mysql:latest"), output);
+}
+#[test]
+fn test_docker_image_namespace_and_repo() {
+    let docker_image = DockerImage {
+        dockerhub_user: Some("trydirect".to_string()),
+        dockerhub_name: Some("mysql".to_string()),
+        dockerhub_image: Some("".to_string(),)
+    };
+    let output = docker_image.to_string();
+    assert_eq!(String::from("trydirect/mysql:latest"), output);
+}
+
+#[test]
+fn test_docker_image_namespace_and_repo_tag() {
+    let docker_image = DockerImage {
+        dockerhub_user: Some("trydirect".to_string()),
+        dockerhub_name: Some("mysql:8.1".to_string()),
+        dockerhub_image: Some("".to_string(),)
+    };
+    let output = docker_image.to_string();
+    assert_eq!(String::from("trydirect/mysql:8.1"), output);
+}
+#[test]
+fn test_docker_image_only_image() {
+    let docker_image = DockerImage {
+        dockerhub_user: None,
+        dockerhub_name: None,
+        dockerhub_image: Some("trydirect/mysql:stable".to_string(),)
+    };
+    let output = docker_image.to_string();
+    assert_eq!(String::from("trydirect/mysql:stable"), output);
+}
+
+#[test]
+fn test_docker_image_only_image_other_empty() {
+    let docker_image = DockerImage {
+        dockerhub_user: Some("".to_string()),
+        dockerhub_name: Some("".to_string()),
+        dockerhub_image: Some("trydirect/mysql:stable".to_string())
+    };
+    let output = docker_image.to_string();
+    assert_eq!(String::from("trydirect/mysql:stable"), output);
+}
+#[test]
+fn test_docker_repo_name_with_tag_other_none() {
+    let docker_image = DockerImage {
+        dockerhub_user: None,
+        dockerhub_name: Some("mysql:stable".to_string()),
+        dockerhub_image: None
+    };
+    let output = docker_image.to_string();
+    assert_eq!(String::from("mysql:stable"), output);
+}

From 37f8d7a09bcec40f5c5b0db98588615865b7967e Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 13 Jan 2024 23:32:14 +0200
Subject: [PATCH 127/284] issue-auth dctypes

---
 .../stack/dctypes/advanced_build_step.rs      |  23 +++
 .../dctypes/advanced_network_settings.rs      |  12 ++
 .../stack/dctypes/advanced_networks.rs        |  13 ++
 src/helpers/stack/dctypes/build_args.rs       |  16 ++
 src/helpers/stack/dctypes/build_step.rs       |   9 +
 src/helpers/stack/dctypes/compose_volume.rs   |  25 +++
 src/helpers/stack/dctypes/external_volume.rs  |   8 +
 src/helpers/stack/dctypes/labels.rs           |  31 +++
 src/helpers/stack/dctypes/mod.rs              | 195 +++---------------
 src/helpers/stack/dctypes/networks.rs         |  24 +++
 src/helpers/stack/dctypes/tmpfs.rs            |   9 +
 .../stack/dctypes/top_level_volumes.rs        |  19 ++
 src/helpers/stack/dctypes/ulimit.rs           |   8 +
 src/helpers/stack/dctypes/ulimits.rs          |  20 ++
 14 files changed, 243 insertions(+), 169 deletions(-)
 create mode 100644 src/helpers/stack/dctypes/advanced_build_step.rs
 create mode 100644 src/helpers/stack/dctypes/advanced_network_settings.rs
 create mode 100644 src/helpers/stack/dctypes/advanced_networks.rs
 create mode 100644 src/helpers/stack/dctypes/build_args.rs
 create mode 100644 src/helpers/stack/dctypes/build_step.rs
 create mode 100644 src/helpers/stack/dctypes/compose_volume.rs
 create mode 100644 src/helpers/stack/dctypes/external_volume.rs
 create mode 100644 src/helpers/stack/dctypes/labels.rs
 create mode 100644 src/helpers/stack/dctypes/networks.rs
 create mode 100644 src/helpers/stack/dctypes/tmpfs.rs
 create mode 100644 src/helpers/stack/dctypes/top_level_volumes.rs
 create mode 100644 src/helpers/stack/dctypes/ulimit.rs
 create mode 100644 src/helpers/stack/dctypes/ulimits.rs

diff --git a/src/helpers/stack/dctypes/advanced_build_step.rs b/src/helpers/stack/dctypes/advanced_build_step.rs
new file mode 100644
index 00000000..c0e6e060
--- /dev/null
+++ b/src/helpers/stack/dctypes/advanced_build_step.rs
@@ -0,0 +1,23 @@
+use serde::{Deserialize, Serialize};
+use crate::helpers::stack::dctypes::*;
+
+#[derive(Builder, Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Default)]
+#[serde(deny_unknown_fields)]
+#[builder(setter(into), default)]
+pub struct AdvancedBuildStep {
+    pub context: String,
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub dockerfile: Option<String>,
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub args: Option<BuildArgs>,
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub shm_size: Option<u64>,
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub target: Option<String>,
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub network: Option<String>,
+    #[serde(default, skip_serializing_if = "Vec::is_empty")]
+    pub cache_from: Vec<String>,
+    #[serde(default, skip_serializing_if = "Labels::is_empty")]
+    pub labels: Labels,
+}
diff --git a/src/helpers/stack/dctypes/advanced_network_settings.rs b/src/helpers/stack/dctypes/advanced_network_settings.rs
new file mode 100644
index 00000000..730c9cb5
--- /dev/null
+++ b/src/helpers/stack/dctypes/advanced_network_settings.rs
@@ -0,0 +1,12 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq, Hash)]
+#[serde(deny_unknown_fields)]
+pub struct AdvancedNetworkSettings {
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub ipv4_address: Option<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub ipv6_address: Option<String>,
+    #[serde(default, skip_serializing_if = "Vec::is_empty")]
+    pub aliases: Vec<String>,
+}
diff --git a/src/helpers/stack/dctypes/advanced_networks.rs b/src/helpers/stack/dctypes/advanced_networks.rs
new file mode 100644
index 00000000..d26aba1f
--- /dev/null
+++ b/src/helpers/stack/dctypes/advanced_networks.rs
@@ -0,0 +1,13 @@
+use serde::{Deserialize, Serialize};
+#[cfg(feature = "indexmap")]
+use indexmap::IndexMap;
+#[cfg(not(feature = "indexmap"))]
+use std::collections::HashMap;
+use crate::helpers::stack::dctypes::*;
+
+#[cfg(feature = "indexmap")]
+#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
+pub struct AdvancedNetworks(pub IndexMap<String, MapOrEmpty<AdvancedNetworkSettings>>);
+#[cfg(not(feature = "indexmap"))]
+#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
+pub struct AdvancedNetworks(pub HashMap<String, MapOrEmpty<AdvancedNetworkSettings>>);
diff --git a/src/helpers/stack/dctypes/build_args.rs b/src/helpers/stack/dctypes/build_args.rs
new file mode 100644
index 00000000..8f629662
--- /dev/null
+++ b/src/helpers/stack/dctypes/build_args.rs
@@ -0,0 +1,16 @@
+use serde::{Deserialize, Serialize};
+#[cfg(feature = "indexmap")]
+use indexmap::IndexMap;
+#[cfg(not(feature = "indexmap"))]
+use std::collections::HashMap;
+
+#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
+#[serde(untagged)]
+pub enum BuildArgs {
+    Simple(String),
+    List(Vec<String>),
+    #[cfg(feature = "indexmap")]
+    KvPair(IndexMap<String, String>),
+    #[cfg(not(feature = "indexmap"))]
+    KvPair(HashMap<String, String>),
+}
diff --git a/src/helpers/stack/dctypes/build_step.rs b/src/helpers/stack/dctypes/build_step.rs
new file mode 100644
index 00000000..25a468d6
--- /dev/null
+++ b/src/helpers/stack/dctypes/build_step.rs
@@ -0,0 +1,9 @@
+use serde::{Deserialize, Serialize};
+use crate::helpers::stack::dctypes;
+
+#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
+#[serde(untagged)]
+pub enum BuildStep {
+    Simple(String),
+    Advanced(dctypes::AdvancedBuildStep),
+}
diff --git a/src/helpers/stack/dctypes/compose_volume.rs b/src/helpers/stack/dctypes/compose_volume.rs
new file mode 100644
index 00000000..4d40b23f
--- /dev/null
+++ b/src/helpers/stack/dctypes/compose_volume.rs
@@ -0,0 +1,25 @@
+use serde::{Deserialize, Serialize};
+use crate::helpers::stack::dctypes::*;
+#[cfg(feature = "indexmap")]
+use indexmap::IndexMap;
+#[cfg(not(feature = "indexmap"))]
+use std::collections::HashMap;
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
+pub struct ComposeVolume {
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub driver: Option<String>,
+    #[cfg(feature = "indexmap")]
+    #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
+    pub driver_opts: IndexMap<String, Option<SingleValue>>,
+    #[cfg(not(feature = "indexmap"))]
+    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
+    pub driver_opts: HashMap<String, Option<SingleValue>>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub external: Option<ExternalVolume>,
+    #[serde(default, skip_serializing_if = "Labels::is_empty")]
+    pub labels: Labels,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub name: Option<String>,
+}
+
diff --git a/src/helpers/stack/dctypes/external_volume.rs b/src/helpers/stack/dctypes/external_volume.rs
new file mode 100644
index 00000000..ba03e26b
--- /dev/null
+++ b/src/helpers/stack/dctypes/external_volume.rs
@@ -0,0 +1,8 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
+#[serde(untagged)]
+pub enum ExternalVolume {
+    Bool(bool),
+    Name { name: String },
+}
diff --git a/src/helpers/stack/dctypes/labels.rs b/src/helpers/stack/dctypes/labels.rs
new file mode 100644
index 00000000..e4296afa
--- /dev/null
+++ b/src/helpers/stack/dctypes/labels.rs
@@ -0,0 +1,31 @@
+use serde::{Deserialize, Serialize};
+#[cfg(feature = "indexmap")]
+use indexmap::IndexMap;
+#[cfg(not(feature = "indexmap"))]
+use std::collections::HashMap;
+
+#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
+#[serde(untagged)]
+pub enum Labels {
+    List(Vec<String>),
+    #[cfg(feature = "indexmap")]
+    Map(IndexMap<String, String>),
+    #[cfg(not(feature = "indexmap"))]
+    Map(HashMap<String, String>),
+}
+
+impl Default for Labels {
+    fn default() -> Self {
+        Self::List(Vec::new())
+    }
+}
+
+impl Labels {
+    pub fn is_empty(&self) -> bool {
+        match self {
+            Self::List(v) => v.is_empty(),
+            Self::Map(m) => m.is_empty(),
+        }
+    }
+}
+
diff --git a/src/helpers/stack/dctypes/mod.rs b/src/helpers/stack/dctypes/mod.rs
index efde67a3..ae2fc4cb 100644
--- a/src/helpers/stack/dctypes/mod.rs
+++ b/src/helpers/stack/dctypes/mod.rs
@@ -14,6 +14,19 @@ mod environment;
 mod extension;
 mod extension_parse_error;
 mod services;
+mod labels;
+mod tmpfs;
+mod ulimit;
+mod ulimits;
+mod networks;
+mod build_step;
+mod advanced_build_step;
+mod build_args;
+mod advanced_networks;
+mod advanced_network_settings;
+mod top_level_volumes;
+mod compose_volume;
+mod external_volume;
 
 pub use port::*;
 pub use published_port::*;
@@ -31,6 +44,19 @@ pub use environment::*;
 pub use extension::*;
 pub use extension_parse_error::*;
 pub use services::*;
+pub use labels::*;
+pub use tmpfs::*;
+pub use ulimit::*;
+pub use ulimits::*;
+pub use networks::*;
+pub use build_step::*;
+pub use advanced_build_step::*;
+pub use build_args::*;
+pub use advanced_networks::*;
+pub use advanced_network_settings::*;
+pub use top_level_volumes::*;
+pub use compose_volume::*;
+pub use external_volume::*;
 
 use crate::helpers::stack::dctypes;
 
@@ -44,175 +70,6 @@ use std::collections::HashMap;
 use std::convert::TryFrom;
 use std::fmt;
 
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
-#[serde(untagged)]
-pub enum Labels {
-    List(Vec<String>),
-    #[cfg(feature = "indexmap")]
-    Map(IndexMap<String, String>),
-    #[cfg(not(feature = "indexmap"))]
-    Map(HashMap<String, String>),
-}
-
-impl Default for Labels {
-    fn default() -> Self {
-        Self::List(Vec::new())
-    }
-}
-
-impl Labels {
-    pub fn is_empty(&self) -> bool {
-        match self {
-            Self::List(v) => v.is_empty(),
-            Self::Map(m) => m.is_empty(),
-        }
-    }
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
-#[serde(untagged)]
-pub enum Tmpfs {
-    Simple(String),
-    List(Vec<String>),
-}
-
-#[cfg(feature = "indexmap")]
-#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
-pub struct Ulimits(pub IndexMap<String, Ulimit>);
-#[cfg(not(feature = "indexmap"))]
-#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
-pub struct Ulimits(pub HashMap<String, Ulimit>);
-
-impl Ulimits {
-    pub fn is_empty(&self) -> bool {
-        self.0.is_empty()
-    }
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
-#[serde(untagged)]
-pub enum Ulimit {
-    Single(i64),
-    SoftHard { soft: i64, hard: i64 },
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
-#[serde(untagged)]
-pub enum Networks {
-    Simple(Vec<String>),
-    Advanced(AdvancedNetworks),
-}
-
-impl Default for Networks {
-    fn default() -> Self {
-        Self::Simple(Vec::new())
-    }
-}
-
-impl Networks {
-    pub fn is_empty(&self) -> bool {
-        match self {
-            Self::Simple(n) => n.is_empty(),
-            Self::Advanced(n) => n.0.is_empty(),
-        }
-    }
-}
-
-#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
-#[serde(untagged)]
-pub enum BuildStep {
-    Simple(String),
-    Advanced(AdvancedBuildStep),
-}
-
-#[derive(Builder, Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Default)]
-#[serde(deny_unknown_fields)]
-#[builder(setter(into), default)]
-pub struct AdvancedBuildStep {
-    pub context: String,
-    #[serde(default, skip_serializing_if = "Option::is_none")]
-    pub dockerfile: Option<String>,
-    #[serde(default, skip_serializing_if = "Option::is_none")]
-    pub args: Option<BuildArgs>,
-    #[serde(default, skip_serializing_if = "Option::is_none")]
-    pub shm_size: Option<u64>,
-    #[serde(default, skip_serializing_if = "Option::is_none")]
-    pub target: Option<String>,
-    #[serde(default, skip_serializing_if = "Option::is_none")]
-    pub network: Option<String>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub cache_from: Vec<String>,
-    #[serde(default, skip_serializing_if = "Labels::is_empty")]
-    pub labels: Labels,
-}
-
-#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
-#[serde(untagged)]
-pub enum BuildArgs {
-    Simple(String),
-    List(Vec<String>),
-    #[cfg(feature = "indexmap")]
-    KvPair(IndexMap<String, String>),
-    #[cfg(not(feature = "indexmap"))]
-    KvPair(HashMap<String, String>),
-}
-
-#[cfg(feature = "indexmap")]
-#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
-pub struct AdvancedNetworks(pub IndexMap<String, MapOrEmpty<AdvancedNetworkSettings>>);
-#[cfg(not(feature = "indexmap"))]
-#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
-pub struct AdvancedNetworks(pub HashMap<String, MapOrEmpty<AdvancedNetworkSettings>>);
-
-#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(deny_unknown_fields)]
-pub struct AdvancedNetworkSettings {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub ipv4_address: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub ipv6_address: Option<String>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub aliases: Vec<String>,
-}
-
-#[cfg(feature = "indexmap")]
-#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
-pub struct TopLevelVolumes(pub IndexMap<String, MapOrEmpty<ComposeVolume>>);
-#[cfg(not(feature = "indexmap"))]
-#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
-pub struct TopLevelVolumes(pub HashMap<String, MapOrEmpty<ComposeVolume>>);
-
-impl TopLevelVolumes {
-    pub fn is_empty(&self) -> bool {
-        self.0.is_empty()
-    }
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
-pub struct ComposeVolume {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub driver: Option<String>,
-    #[cfg(feature = "indexmap")]
-    #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
-    pub driver_opts: IndexMap<String, Option<SingleValue>>,
-    #[cfg(not(feature = "indexmap"))]
-    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
-    pub driver_opts: HashMap<String, Option<SingleValue>>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub external: Option<ExternalVolume>,
-    #[serde(default, skip_serializing_if = "Labels::is_empty")]
-    pub labels: Labels,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub name: Option<String>,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
-#[serde(untagged)]
-pub enum ExternalVolume {
-    Bool(bool),
-    Name { name: String },
-}
-
 #[cfg(feature = "indexmap")]
 #[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
 pub struct ComposeNetworks(pub IndexMap<String, MapOrEmpty<NetworkSettings>>);
diff --git a/src/helpers/stack/dctypes/networks.rs b/src/helpers/stack/dctypes/networks.rs
new file mode 100644
index 00000000..bbcfdb14
--- /dev/null
+++ b/src/helpers/stack/dctypes/networks.rs
@@ -0,0 +1,24 @@
+use serde::{Deserialize, Serialize};
+use crate::helpers::stack::dctypes;
+
+#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
+#[serde(untagged)]
+pub enum Networks {
+    Simple(Vec<String>),
+    Advanced(dctypes::AdvancedNetworks),
+}
+
+impl Default for Networks {
+    fn default() -> Self {
+        Self::Simple(Vec::new())
+    }
+}
+
+impl Networks {
+    pub fn is_empty(&self) -> bool {
+        match self {
+            Self::Simple(n) => n.is_empty(),
+            Self::Advanced(n) => n.0.is_empty(),
+        }
+    }
+}
diff --git a/src/helpers/stack/dctypes/tmpfs.rs b/src/helpers/stack/dctypes/tmpfs.rs
new file mode 100644
index 00000000..1bea9b6e
--- /dev/null
+++ b/src/helpers/stack/dctypes/tmpfs.rs
@@ -0,0 +1,9 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
+#[serde(untagged)]
+pub enum Tmpfs {
+    Simple(String),
+    List(Vec<String>),
+}
+
diff --git a/src/helpers/stack/dctypes/top_level_volumes.rs b/src/helpers/stack/dctypes/top_level_volumes.rs
new file mode 100644
index 00000000..7216953d
--- /dev/null
+++ b/src/helpers/stack/dctypes/top_level_volumes.rs
@@ -0,0 +1,19 @@
+use serde::{Deserialize, Serialize};
+#[cfg(feature = "indexmap")]
+use indexmap::IndexMap;
+#[cfg(not(feature = "indexmap"))]
+use std::collections::HashMap;
+use crate::helpers::stack::dctypes::*;
+
+#[cfg(feature = "indexmap")]
+#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
+pub struct TopLevelVolumes(pub IndexMap<String, MapOrEmpty<ComposeVolume>>);
+#[cfg(not(feature = "indexmap"))]
+#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
+pub struct TopLevelVolumes(pub HashMap<String, MapOrEmpty<ComposeVolume>>);
+
+impl TopLevelVolumes {
+    pub fn is_empty(&self) -> bool {
+        self.0.is_empty()
+    }
+}
diff --git a/src/helpers/stack/dctypes/ulimit.rs b/src/helpers/stack/dctypes/ulimit.rs
new file mode 100644
index 00000000..65899310
--- /dev/null
+++ b/src/helpers/stack/dctypes/ulimit.rs
@@ -0,0 +1,8 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
+#[serde(untagged)]
+pub enum Ulimit {
+    Single(i64),
+    SoftHard { soft: i64, hard: i64 },
+}
diff --git a/src/helpers/stack/dctypes/ulimits.rs b/src/helpers/stack/dctypes/ulimits.rs
new file mode 100644
index 00000000..721eaf96
--- /dev/null
+++ b/src/helpers/stack/dctypes/ulimits.rs
@@ -0,0 +1,20 @@
+use serde::{Deserialize, Serialize};
+#[cfg(feature = "indexmap")]
+use indexmap::IndexMap;
+#[cfg(not(feature = "indexmap"))]
+use std::collections::HashMap;
+use crate::helpers::stack::dctypes;
+
+#[cfg(feature = "indexmap")]
+#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
+pub struct Ulimits(pub IndexMap<String, dctypes::Ulimit>);
+#[cfg(not(feature = "indexmap"))]
+#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
+pub struct Ulimits(pub HashMap<String, dctypes::Ulimit>);
+
+impl Ulimits {
+    pub fn is_empty(&self) -> bool {
+        self.0.is_empty()
+    }
+}
+

From c95fe1ffee0a3f17b3e6e3e478bb23a671b005e0 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sun, 14 Jan 2024 09:50:24 +0200
Subject: [PATCH 128/284] issue-auth dctypes

---
 src/helpers/stack/dctypes/compose.rs          | 20 ++---
 src/helpers/stack/dctypes/compose_network.rs  |  9 ++
 .../compose_network_setting_details.rs        |  7 ++
 src/helpers/stack/dctypes/compose_networks.rs | 19 ++++
 .../dctypes/external_network_setting_bool.rs  |  5 ++
 src/helpers/stack/dctypes/ipam.rs             | 12 +++
 src/helpers/stack/dctypes/ipam_config.rs      |  9 ++
 src/helpers/stack/dctypes/mod.rs              | 88 +++----------------
 src/helpers/stack/dctypes/network_settings.rs | 29 ++++++
 9 files changed, 114 insertions(+), 84 deletions(-)
 create mode 100644 src/helpers/stack/dctypes/compose_network.rs
 create mode 100644 src/helpers/stack/dctypes/compose_network_setting_details.rs
 create mode 100644 src/helpers/stack/dctypes/compose_networks.rs
 create mode 100644 src/helpers/stack/dctypes/external_network_setting_bool.rs
 create mode 100644 src/helpers/stack/dctypes/ipam.rs
 create mode 100644 src/helpers/stack/dctypes/ipam_config.rs
 create mode 100644 src/helpers/stack/dctypes/network_settings.rs

diff --git a/src/helpers/stack/dctypes/compose.rs b/src/helpers/stack/dctypes/compose.rs
index 82372d6a..46d43c27 100644
--- a/src/helpers/stack/dctypes/compose.rs
+++ b/src/helpers/stack/dctypes/compose.rs
@@ -1,5 +1,5 @@
 use serde::{Deserialize, Serialize};
-use crate::helpers::stack::dctypes;
+use crate::helpers::stack::dctypes::*;
 #[cfg(feature = "indexmap")]
 use indexmap::IndexMap;
 use serde_yaml::Value;
@@ -8,18 +8,18 @@ use serde_yaml::Value;
 pub struct Compose {
     #[serde(skip_serializing_if = "Option::is_none")]
     pub version: Option<String>,
-    #[serde(default, skip_serializing_if = "dctypes::Services::is_empty")]
-    pub services: dctypes::Services,
-    #[serde(default, skip_serializing_if = "dctypes::TopLevelVolumes::is_empty")]
-    pub volumes: dctypes::TopLevelVolumes,
-    #[serde(default, skip_serializing_if = "dctypes::ComposeNetworks::is_empty")]
-    pub networks: dctypes::ComposeNetworks,
+    #[serde(default, skip_serializing_if = "Services::is_empty")]
+    pub services: Services,
+    #[serde(default, skip_serializing_if = "TopLevelVolumes::is_empty")]
+    pub volumes: TopLevelVolumes,
+    #[serde(default, skip_serializing_if = "ComposeNetworks::is_empty")]
+    pub networks: ComposeNetworks,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub service: Option<dctypes::Service>,
+    pub service: Option<Service>,
     #[cfg(feature = "indexmap")]
     #[serde(flatten, skip_serializing_if = "IndexMap::is_empty")]
-    pub extensions: IndexMap<dctypes::Extension, Value>,
+    pub extensions: IndexMap<Extension, Value>,
     #[cfg(not(feature = "indexmap"))]
     #[serde(flatten, skip_serializing_if = "HashMap::is_empty")]
-    pub extensions: HashMap<dctypes::Extension, Value>,
+    pub extensions: HashMap<Extension, Value>,
 }
diff --git a/src/helpers/stack/dctypes/compose_network.rs b/src/helpers/stack/dctypes/compose_network.rs
new file mode 100644
index 00000000..da364349
--- /dev/null
+++ b/src/helpers/stack/dctypes/compose_network.rs
@@ -0,0 +1,9 @@
+use serde::{Deserialize, Serialize};
+use crate::helpers::stack::dctypes::*;
+
+#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
+#[serde(untagged)]
+pub enum ComposeNetwork {
+    Detailed(ComposeNetworkSettingDetails),
+    Bool(bool),
+}
diff --git a/src/helpers/stack/dctypes/compose_network_setting_details.rs b/src/helpers/stack/dctypes/compose_network_setting_details.rs
new file mode 100644
index 00000000..59d0348c
--- /dev/null
+++ b/src/helpers/stack/dctypes/compose_network_setting_details.rs
@@ -0,0 +1,7 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
+#[serde(deny_unknown_fields)]
+pub struct ComposeNetworkSettingDetails {
+    pub name: String,
+}
diff --git a/src/helpers/stack/dctypes/compose_networks.rs b/src/helpers/stack/dctypes/compose_networks.rs
new file mode 100644
index 00000000..f1374f0c
--- /dev/null
+++ b/src/helpers/stack/dctypes/compose_networks.rs
@@ -0,0 +1,19 @@
+#[cfg(feature = "indexmap")]
+use indexmap::IndexMap;
+#[cfg(not(feature = "indexmap"))]
+use std::collections::HashMap;
+use serde::{Deserialize, Serialize};
+use crate::helpers::stack::dctypes::*;
+
+#[cfg(feature = "indexmap")]
+#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
+pub struct ComposeNetworks(pub IndexMap<String, MapOrEmpty<NetworkSettings>>);
+#[cfg(not(feature = "indexmap"))]
+#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
+pub struct ComposeNetworks(pub HashMap<String, MapOrEmpty<NetworkSettings>>);
+
+impl ComposeNetworks {
+    pub fn is_empty(&self) -> bool {
+        self.0.is_empty()
+    }
+}
diff --git a/src/helpers/stack/dctypes/external_network_setting_bool.rs b/src/helpers/stack/dctypes/external_network_setting_bool.rs
new file mode 100644
index 00000000..1d1ad3d3
--- /dev/null
+++ b/src/helpers/stack/dctypes/external_network_setting_bool.rs
@@ -0,0 +1,5 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
+#[serde(deny_unknown_fields)]
+pub struct ExternalNetworkSettingBool(bool);
diff --git a/src/helpers/stack/dctypes/ipam.rs b/src/helpers/stack/dctypes/ipam.rs
new file mode 100644
index 00000000..592c2a6d
--- /dev/null
+++ b/src/helpers/stack/dctypes/ipam.rs
@@ -0,0 +1,12 @@
+use serde::{Deserialize, Serialize};
+use crate::helpers::stack::dctypes::*;
+
+#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
+#[serde(deny_unknown_fields)]
+pub struct Ipam {
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub driver: Option<String>,
+    #[serde(default, skip_serializing_if = "Vec::is_empty")]
+    pub config: Vec<IpamConfig>,
+}
+
diff --git a/src/helpers/stack/dctypes/ipam_config.rs b/src/helpers/stack/dctypes/ipam_config.rs
new file mode 100644
index 00000000..f40c2f59
--- /dev/null
+++ b/src/helpers/stack/dctypes/ipam_config.rs
@@ -0,0 +1,9 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
+#[serde(deny_unknown_fields)]
+pub struct IpamConfig {
+    pub subnet: String,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub gateway: Option<String>,
+}
diff --git a/src/helpers/stack/dctypes/mod.rs b/src/helpers/stack/dctypes/mod.rs
index ae2fc4cb..66bda096 100644
--- a/src/helpers/stack/dctypes/mod.rs
+++ b/src/helpers/stack/dctypes/mod.rs
@@ -27,6 +27,13 @@ mod advanced_network_settings;
 mod top_level_volumes;
 mod compose_volume;
 mod external_volume;
+mod compose_network;
+mod compose_networks;
+mod compose_network_setting_details;
+mod external_network_setting_bool;
+mod network_settings;
+mod ipam;
+mod ipam_config;
 
 pub use port::*;
 pub use published_port::*;
@@ -57,6 +64,13 @@ pub use advanced_network_settings::*;
 pub use top_level_volumes::*;
 pub use compose_volume::*;
 pub use external_volume::*;
+pub use compose_networks::*;
+pub use compose_network::*;
+pub use compose_network_setting_details::*;
+pub use external_network_setting_bool::*;
+pub use network_settings::*;
+pub use ipam::*;
+pub use ipam_config::*;
 
 use crate::helpers::stack::dctypes;
 
@@ -70,80 +84,6 @@ use std::collections::HashMap;
 use std::convert::TryFrom;
 use std::fmt;
 
-#[cfg(feature = "indexmap")]
-#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
-pub struct ComposeNetworks(pub IndexMap<String, MapOrEmpty<NetworkSettings>>);
-
-#[cfg(not(feature = "indexmap"))]
-#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
-pub struct ComposeNetworks(pub HashMap<String, MapOrEmpty<NetworkSettings>>);
-
-impl ComposeNetworks {
-    pub fn is_empty(&self) -> bool {
-        self.0.is_empty()
-    }
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(untagged)]
-pub enum ComposeNetwork {
-    Detailed(ComposeNetworkSettingDetails),
-    Bool(bool),
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(deny_unknown_fields)]
-pub struct ComposeNetworkSettingDetails {
-    pub name: String,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(deny_unknown_fields)]
-pub struct ExternalNetworkSettingBool(bool);
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
-#[serde(deny_unknown_fields)]
-pub struct NetworkSettings {
-    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
-    pub attachable: bool,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub driver: Option<String>,
-    #[cfg(feature = "indexmap")]
-    #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
-    pub driver_opts: IndexMap<String, Option<SingleValue>>,
-    #[cfg(not(feature = "indexmap"))]
-    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
-    pub driver_opts: HashMap<String, Option<SingleValue>>,
-    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
-    pub enable_ipv6: bool,
-    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
-    pub internal: bool,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub external: Option<ComposeNetwork>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub ipam: Option<Ipam>,
-    #[serde(default, skip_serializing_if = "Labels::is_empty")]
-    pub labels: Labels,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub name: Option<String>,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(deny_unknown_fields)]
-pub struct Ipam {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub driver: Option<String>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub config: Vec<IpamConfig>,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(deny_unknown_fields)]
-pub struct IpamConfig {
-    pub subnet: String,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub gateway: Option<String>,
-}
 
 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
 #[serde(deny_unknown_fields)]
diff --git a/src/helpers/stack/dctypes/network_settings.rs b/src/helpers/stack/dctypes/network_settings.rs
new file mode 100644
index 00000000..9c45bc15
--- /dev/null
+++ b/src/helpers/stack/dctypes/network_settings.rs
@@ -0,0 +1,29 @@
+use serde::{Deserialize, Serialize};
+use crate::helpers::stack::dctypes::*;
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
+#[serde(deny_unknown_fields)]
+pub struct NetworkSettings {
+    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
+    pub attachable: bool,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub driver: Option<String>,
+    #[cfg(feature = "indexmap")]
+    #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
+    pub driver_opts: IndexMap<String, Option<SingleValue>>,
+    #[cfg(not(feature = "indexmap"))]
+    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
+    pub driver_opts: HashMap<String, Option<SingleValue>>,
+    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
+    pub enable_ipv6: bool,
+    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
+    pub internal: bool,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub external: Option<ComposeNetwork>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub ipam: Option<Ipam>,
+    #[serde(default, skip_serializing_if = "Labels::is_empty")]
+    pub labels: Labels,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub name: Option<String>,
+}

From 2cb0723f6eedc1212c25d310cb43a5399c2539a5 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Mon, 15 Jan 2024 12:54:30 +0200
Subject: [PATCH 129/284] environment variables structure is changed, service
 _id type is changed to String (reason: simplifies front end functionality)

---
 configuration.yaml                        | 16 +++++
 docker-compose.yml                        |  2 +-
 src/forms/stack.rs                        | 74 +++++++++++++----------
 src/helpers/stack/builder.rs              | 12 ++--
 tests/custom-stack-payload-11.json        |  1 -
 tests/custom-stack-payload-12.json        |  1 -
 tests/custom-stack-payload-2.json         |  1 -
 tests/custom-stack-payload-3.json         |  1 -
 tests/custom-stack-payload-4.json         |  1 -
 tests/custom-stack-payload-5.json         |  1 -
 tests/custom-stack-payload-6.json         |  1 -
 tests/custom-stack-payload-7.json         |  1 -
 tests/custom-stack-payload-8.json         |  1 -
 tests/custom-stack-payload-9.json         |  1 -
 tests/custom-stack-payload-singleapp.json |  1 -
 tests/custom-stack-payload.json           |  5 +-
 tests/model_user_stack.rs                 | 21 ++++---
 17 files changed, 80 insertions(+), 61 deletions(-)
 create mode 100644 configuration.yaml
 delete mode 100644 tests/custom-stack-payload-11.json
 delete mode 100644 tests/custom-stack-payload-12.json
 delete mode 100644 tests/custom-stack-payload-2.json
 delete mode 100644 tests/custom-stack-payload-3.json
 delete mode 100644 tests/custom-stack-payload-4.json
 delete mode 100644 tests/custom-stack-payload-5.json
 delete mode 100644 tests/custom-stack-payload-6.json
 delete mode 100644 tests/custom-stack-payload-7.json
 delete mode 100644 tests/custom-stack-payload-8.json
 delete mode 100644 tests/custom-stack-payload-9.json
 delete mode 100644 tests/custom-stack-payload-singleapp.json

diff --git a/configuration.yaml b/configuration.yaml
new file mode 100644
index 00000000..2e5d2968
--- /dev/null
+++ b/configuration.yaml
@@ -0,0 +1,16 @@
+app_host: 127.0.0.1
+app_port: 8000
+auth_url: https://dev.try.direct/server/user/oauth_server/api/me
+max_clients_number: 2
+database:
+  host: 127.0.0.1
+  port: 5432
+  username: postgres
+  password: postgres
+  database_name: stacker
+
+amqp:
+  host: 51.15.74.139
+  port: 5672
+  username: guest
+  password: rabbitdev2023rabbitmqctl@@
diff --git a/docker-compose.yml b/docker-compose.yml
index 9cba7b20..9a66dde3 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -8,7 +8,7 @@ volumes:
 services:
 
   stacker:
-    image: trydirect/stacker:0.0.5
+    image: trydirect/stacker:0.0.6
     build: .
     container_name: stacker
     restart: always
diff --git a/src/forms/stack.rs b/src/forms/stack.rs
index 0459e662..205aae36 100644
--- a/src/forms/stack.rs
+++ b/src/forms/stack.rs
@@ -4,6 +4,7 @@ use serde_valid::Validate;
 use std::collections::HashMap;
 use std::fmt;
 use crate::helpers::{login, docker_image_exists};
+use tokio::runtime::Runtime;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Role {
@@ -28,46 +29,51 @@ pub struct Requirements {
     pub ram_size: Option<String>,
 }
 
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Port {
+    #[validate(pattern = r"^\d+$")]
     pub host_port: Option<String>,
+    #[validate(pattern = r"^\d+$")]
     pub container_port: Option<String>,
+    #[validate(enumerate("tcp", "udp"))]
+    pub protocol: Option<String>
 }
 
-async fn validate_dockerhub_image(
-    dockerhub_user: &Option<String>,
-    dockerhub_password: &Option<String>,
-    dockerhub_name: &Option<String>) -> Result<(), serde_valid::validation::Error> {
+fn validate_dockerhub_image(docker_image: DockerImage) -> Result<(), serde_valid::validation::Error> {
+
+    // Create the runtime
+    let rt = Runtime::new().unwrap();
+
+    // Spawn a blocking function onto the runtime
+    rt.block_on(async {
+        let result = login(
+            docker_image.dockerhub_user.clone().unwrap_or("".to_string()).as_ref(),
+            docker_image.dockerhub_password.clone().unwrap_or("".to_string()).as_ref()
+        )
+            .await
+            .map_err(|err| serde_valid::validation::Error::Custom(format!("{:?}", err)))?;
 
-    let result = login(
-        dockerhub_user.clone().unwrap_or("".to_string()).as_ref(),
-        dockerhub_password.clone().unwrap_or("".to_string()).as_ref()
-    ).await;
+        match result.token {
+            None => {
+                return Err(serde_valid::validation::Error::Custom(
+                    "Could not access docker image repository, please check credentials.".to_owned(),
+                ));
+            },
+            Some(tok) => {
 
-    let exists: bool = match result.unwrap().token {
-        Some(tok) => {
-            let exists = docker_image_exists(
-                dockerhub_user.clone().unwrap().as_str(),
-                dockerhub_name.clone().unwrap().as_str(), tok).await;
-            match exists {
-                Ok(_) => true,
-                Err(err) => {
-                    println!("{:?}", err);
-                    false
-                }
+                tracing::debug!("We were able to login hub.docker.com!");
+                docker_image_exists(
+                    docker_image.dockerhub_user.clone().unwrap().as_str(),
+                    docker_image.dockerhub_name.clone().unwrap().as_str(), tok)
+                    .await
+                    .map_err(|err| serde_valid::validation::Error::Custom("Not exists".to_string()))
+                    .map(|_| ())
             }
         }
-        _ => false,
-    };
-    if !exists {
-        return Err(serde_valid::validation::Error::Custom(
-            "Could not access docker image repository, please check credentials.".to_owned(),
-        ));
-    }
-    Ok(())
+    })
 }
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-#[validate(custom(|s| validate_dockerhub_image(dockerhub_user,dockerhub_name, dockerhub_password)))]
+#[validate(custom(|docker_image| validate_dockerhub_image(docker_image)))]
 pub struct DockerImage {
     #[validate(min_length = 3)]
     #[validate(max_length = 50)]
@@ -238,7 +244,7 @@ pub struct App {
     #[validate(max_length = 255)]
     pub etag: Option<String>,
     #[serde(rename = "_id")]
-    pub id: u32,
+    pub id: String,
     #[serde(rename = "_created")]
     pub created: Option<String>,
     #[serde(rename = "_updated")]
@@ -295,9 +301,15 @@ pub struct App {
     pub ports: Option<Vec<Port>>,
 }
 
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct EnvVar {
+    pub(crate) key: String,
+    pub(crate) value: String
+}
+
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct Environment {
-    pub(crate) environment: Option<Vec<HashMap<String, String>>>,
+    pub(crate) environment: Option<Vec<EnvVar>>,
 }
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct Volume {
diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index 3df222b3..77b648c1 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -138,14 +138,12 @@ impl TryIntoService for App {
             .collect();
 
         let mut envs = IndexMap::new();
-        for item in self.environment.environment.clone().unwrap_or_default() {
-            let items = item
-                .into_iter()
-                .map(|(k, v)| (k, Some(SingleValue::String(v.clone()))))
-                .collect::<IndexMap<_,_>>();
+        let items = self.environment.environment.clone().unwrap_or_default()
+            .into_iter()
+            .map(|env_var| (env_var.key, Some(SingleValue::String(env_var.value.clone()))))
+            .collect::<IndexMap<_,_>>();
 
-            envs.extend(items);
-        }
+        envs.extend(items);
 
         service.networks = networks;
         service.ports = Ports::Long(ports);
diff --git a/tests/custom-stack-payload-11.json b/tests/custom-stack-payload-11.json
deleted file mode 100644
index 811f74f6..00000000
--- a/tests/custom-stack-payload-11.json
+++ /dev/null
@@ -1 +0,0 @@
-{"commonDomain":"justates222t.com","domainList":{},"region":"fsn1","zone":null,"server":"cx21","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":["portainer_ce_feature"],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":true,"disk_type":"pd-standart","cloud_token":"***********","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"_etag":null,"_id":180,"_created":"2021-12-17T08:11:40.875486","_updated":"2023-11-21T17:37:24.594545","name":"Airflow","code":"airflow","role":["airflow"],"type":"web","default":true,"popularity":null,"descr":null,"ports":{"private":["31"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":150,"height":150,"image":"7f41d873-2de5-4c6e-a037-42eeff572db6.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\">Airflow description </p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.0","ram_size":null,"disk_size":null,"dockerhub_image":"airflow","form":null,"category":[null],"group":[],"versions":[{"_id":425,"name":"Airflow","version":"2.7.3","update_status":"published","tag":"latest"},{"_id":426,"name":"Airflow","version":"2.7.1-unstable","update_status":"published","tag":"stable"}],"links":[],"domain":"latestports.com","sharedPorts":[{"host_port":"5000","container_port":"5000"}],"main":true,"version":{"_id":426,"name":"Airflow","version":"2.7.1-unstable","update_status":"published","tag":"stable"},"environment":[{"ENV_VAR1":"ENV_VAR1_VALUE"}, {"ENV_VAR2":"ENV_VAR1_VALUe2"}],"network":["testnetwork"],"restart":"always","volumes":[{"host_path":"./airflow/host/path/","container_path":"/airflow/container/path"}, {"host_path":"./airflow/host/path2/","container_path":"/airflow/container/path2"}, {"host_path":"airflow","container_path":"/airflow/named/volume/path"}]},{"category":[{"name":"integrated_features"}],"name":"CustomNginx","code":"customnginx","domain":"customnginx.latestports.com","sharedPorts":[{"host_port":"80","container_port":"8080"},{"host_port":"443","container_port":"8443"}],"versions":[],"custom":true,"type":"web","main":false,"_id":0,"dockerhub_user":"trydirect","dockerhub_name":"nginx","disk_size":"0.9Gb","ram_size":"1.3Gb","cpu":"0.3","volumes":[{"host_path":"./host/path","container_path":"/container/path"}, {"host_path":  "namedvolume", "container_path": "/container/path/to/"}],"environment":[{"CUSTOM_NGINX_ENV_VAR1":"CUSTOM_NGINX_ENV_VAR1_VALUE"},{"CUSTOM_NGINX_VAR2": "CUSTOM_NGINX_VAR_VALUE2"}, {"CUSTOM_NGINX_ENV_VAR3":"CUSTOM_NGINX_ENV_VAR1_VALUE3"},{"CUSTOM_NGINX_VAR4": "CUSTOM_NGINX_VAR_VALUE4"}],"network":["testnetwork"],"restart":"always"}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\"><strong style=\"font-weight:bold\">Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)</strong></p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.6","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","form":null,"category":[{"name":"integrated_features"}],"group":[],"versions":[{"_id":456,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"}],"links":[],"version":{"_id":456,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"},"sharedPorts":[{"host_port":"9000","container_port":"9000"}],"environment":[{"PORTAINER_ENV_VAR1":"PORTAINER_ENV_VAR1_VALUE"}],"volumes":[{"host_path":"/portainer/test/path","container_path":"/portainer/test/path"}],"network":["testnetwork"],"restart":"always"}],"service":[{"_etag":null,"_id":24,"_created":"2020-06-19T13:07:24.228389","_updated":"2023-11-23T10:18:28.095813","name":"PostgreSQL","code":"postgres","role":["postgres"],"type":"service","default":null,"popularity":null,"descr":null,"ports":{"private":["5432"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":576,"height":594,"image":"fd23f54c-e250-4228-8d56-7e5d93ffb925.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":null,"plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.0","ram_size":null,"disk_size":null,"dockerhub_image":"postgres","form":null,"category":[null],"group":[{"_etag":null,"_id":12,"_created":null,"_updated":null,"name":"Database","code":"database"}],"versions":[{"_id":458,"name":"15","version":"15","update_status":"published","tag":"15"},{"_id":288,"name":"10.22","version":"10.22","update_status":"published","tag":"10.22"},{"_id":303,"name":"13.8","version":"13.8","update_status":"published","tag":"13.8"},{"_id":266,"name":"11","version":"11","update_status":"published","tag":"11"},{"_id":267,"name":"12.12","version":"12.12","update_status":"published","tag":"12.12"},{"_id":38,"name":"14.5","version":"14.5","update_status":"published","tag":"14.5"},{"_id":596,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"}],"links":[],"domain":"","sharedPorts":[{"host_port":"5432","container_port":"5432"}],"main":false,"version":{"_id":596,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"},"environment":[{"POSTGRES_ENV_VAR1":"POSTGRES_ENV_VAR1_VALUE"},{"POSTGRES_ENV_VAR2":"POSTGRES_ENV_VAR2_VALUE"}],"network":["testnetwork"],"restart":"always","volumes":[{"host_path":"./pg.conf","container_path":"/etc/postgres/pg.conf"}]}],"servers_count":3,"project_name":"LatestPorts","custom_stack_code":"latestports","project_git_url":"https://github.com/latestports","custom_stack_category":[],"networks":["testnetwork","testnetwork2"]}}
\ No newline at end of file
diff --git a/tests/custom-stack-payload-12.json b/tests/custom-stack-payload-12.json
deleted file mode 100644
index d405ff42..00000000
--- a/tests/custom-stack-payload-12.json
+++ /dev/null
@@ -1 +0,0 @@
-{"commonDomain":"justates222tee.com","domainList":{},"region":"fsn1","zone":null,"server":"cx21","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":["portainer_ce_feature"],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":true,"disk_type":"pd-standart","cloud_token":"***********","provider":"htz","stack_code":"custom-stack-2","selected_plan":"plan-individual-monthly","custom":{"web":[{"_etag":null,"_id":180,"_created":"2021-12-17T08:11:40.875486","_updated":"2023-11-21T17:37:24.594545","name":"Airflow","code":"airflow","role":["airflow"],"type":"web","default":true,"popularity":null,"descr":null,"ports":{"private":["31"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":150,"height":150,"image":"7f41d873-2de5-4c6e-a037-42eeff572db6.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\">Airflow description </p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.0","ram_size":null,"disk_size":null,"dockerhub_image":"airflow","form":null,"category":[null],"group":[],"versions":[{"_id":425,"name":"Airflow","version":"2.7.3","update_status":"published","tag":"latest"},{"_id":426,"name":"Airflow","version":"2.7.1-unstable","update_status":"published","tag":"stable"}],"links":[],"domain":"latestports.com","sharedPorts":[{"host_port":"5000","container_port":"5000"}],"main":true,"version":{"_id":426,"name":"Airflow","version":"2.7.1-unstable","update_status":"published","tag":"stable"},"environment":[{"ENV_VAR1":"ENV_VAR1_VALUE"}, {"ENV_VAR2":"ENV_VAR1_VALUe2"}],"network":["testnetwork"],"restart":"always","volumes":[{"host_path":"./airflow/host/path/","container_path":"/airflow/container/path"}, {"host_path":"./airflow/host/path2/","container_path":"/airflow/container/path2"}, {"host_path":"airflow","container_path":"/airflow/named/volume/path"}]},{"category":[{"name":"integrated_features"}],"name":"CustomNginx","code":"customnginx","domain":"customnginx.latestports.com","sharedPorts":[{"host_port":"80","container_port":"8080"},{"host_port":"443","container_port":"8443"}],"versions":[],"custom":true,"type":"web","main":false,"_id":0,"dockerhub_user":"trydirect","dockerhub_name":"nginx","disk_size":"0.9Gb","ram_size":"1.3Gb","cpu":"0.3","volumes":[{"host_path":"./host/path","container_path":"/container/path"}, {"host_path":  "namedvolume", "container_path": "/container/path/to/"}],"environment":[{"CUSTOM_NGINX_ENV_VAR1":"CUSTOM_NGINX_ENV_VAR1_VALUE"},{"CUSTOM_NGINX_VAR2": "CUSTOM_NGINX_VAR_VALUE2"}, {"CUSTOM_NGINX_ENV_VAR3":"CUSTOM_NGINX_ENV_VAR1_VALUE3"},{"CUSTOM_NGINX_VAR4": "CUSTOM_NGINX_VAR_VALUE4"}],"network":["testnetwork"],"restart":"always"}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\"><strong style=\"font-weight:bold\">Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)</strong></p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.6","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","form":null,"category":[{"name":"integrated_features"}],"group":[],"versions":[{"_id":456,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"}],"links":[],"version":{"_id":456,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"},"sharedPorts":[{"host_port":"9000","container_port":"9000"}],"environment":[{"PORTAINER_ENV_VAR1":"PORTAINER_ENV_VAR1_VALUE"}],"volumes":[{"host_path":"/portainer/test/path","container_path":"/portainer/test/path"}],"network":["testnetwork"],"restart":"always"}],"service":[{"_etag":null,"_id":24,"_created":"2020-06-19T13:07:24.228389","_updated":"2023-11-23T10:18:28.095813","name":"PostgreSQL","code":"postgres","role":["postgres"],"type":"service","default":null,"popularity":null,"descr":null,"ports":{"private":["5432"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":576,"height":594,"image":"fd23f54c-e250-4228-8d56-7e5d93ffb925.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":null,"plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.0","ram_size":null,"disk_size":null,"dockerhub_image":"postgres","form":null,"category":[null],"group":[{"_etag":null,"_id":12,"_created":null,"_updated":null,"name":"Database","code":"database"}],"versions":[{"_id":458,"name":"15","version":"15","update_status":"published","tag":"15"},{"_id":288,"name":"10.22","version":"10.22","update_status":"published","tag":"10.22"},{"_id":303,"name":"13.8","version":"13.8","update_status":"published","tag":"13.8"},{"_id":266,"name":"11","version":"11","update_status":"published","tag":"11"},{"_id":267,"name":"12.12","version":"12.12","update_status":"published","tag":"12.12"},{"_id":38,"name":"14.5","version":"14.5","update_status":"published","tag":"14.5"},{"_id":596,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"}],"links":[],"domain":"","sharedPorts":[{"host_port":"5432","container_port":"5432"}],"main":false,"version":{"_id":596,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"},"environment":[{"POSTGRES_ENV_VAR1":"POSTGRES_ENV_VAR1_VALUE"},{"POSTGRES_ENV_VAR2":"POSTGRES_ENV_VAR2_VALUE"}],"network":["testnetwork"],"restart":"always","volumes":[{"host_path":"./pg.conf","container_path":"/etc/postgres/pg.conf"}]}],"servers_count":3,"project_name":"DockerHubCheck1","custom_stack_code":"hubcheck","project_git_url":"https://github.com/latestports","custom_stack_category":[],"networks":["testnetwork","testnetwork2"]}}
\ No newline at end of file
diff --git a/tests/custom-stack-payload-2.json b/tests/custom-stack-payload-2.json
deleted file mode 100644
index e64ec975..00000000
--- a/tests/custom-stack-payload-2.json
+++ /dev/null
@@ -1 +0,0 @@
-{"commonDomain":"","domainList":{},"region":"fsn1","zone":null,"server":"cx21","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":["stack_migration"],"save_token":false,"cloud_token":"r6LAjqrynVt7pUwctVkzBlJmKjLOCxJIWjZFMLTkPYCCB4rsgphhEVhiL4DuO757","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"name":"Smarty Bot","code":"smarty-bot","domain":"smartybot.xyz","sharedPorts":["8000"],"versions":[],"custom":true,"type":"web","main":true,"_id":"lltkpq6p347kystct","dockerhub_user":"trydirect","dockerhub_name":"smarty-bot","url_app":"smartybot.xyz","url_git":"https://github.com/vsilent/smarty.git","disk_size":"1Gb","ram_size":"1Gb","cpu":1}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\"><strong style=\"font-weight:bold\">Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)</strong></p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.6","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","versions":[{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}],"domain":"","sharedPorts":["9000"],"main":true,"version":{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}}],"service":[{"_etag":null,"_id":230,"_created":"2023-05-24T12:51:52.108972","_updated":"2023-08-04T12:18:34.670194","name":"pgrst","code":"pgrst","role":null,"type":"service","default":null,"popularity":null,"descr":null,"ports":null,"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":null,"category_id":null,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\">PostgREST description</p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"1","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"pgrst","versions":[{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"},{"_etag":null,"_id":563,"_created":null,"_updated":"2023-05-24T12:52:15.351522","app_id":230,"name":"0.0.5","version":"0.0.5","update_status":"ready_for_testing","tag":"0.0.5"}],"domain":"","sharedPorts":["9999"],"main":true,"version":{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"}}],"servers_count":3,"custom_stack_name":"mysampleproject","custom_stack_code":"smarty-bot","custom_stack_category":["New"],"custom_stack_short_description":"sample short description","custom_stack_description":"stack description","custom_stack_publish":false,"project_name":"Smarty Bot","project_git_url":"https://github.com/vsilent/smarty.git","project_overview":"my product 1","project_description":"my product 1"}}
diff --git a/tests/custom-stack-payload-3.json b/tests/custom-stack-payload-3.json
deleted file mode 100644
index 4008848f..00000000
--- a/tests/custom-stack-payload-3.json
+++ /dev/null
@@ -1 +0,0 @@
-{"commonDomain":"","domainList":{},"region":"fsn1","zone":null,"server":"cx21","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":["stack_migration"],"save_token":false,"cloud_token":"r6LAjqrynVt7pUwctVkzBlJmKjLOCxJIWjZFMLTkPYCCB4rsgphhEVhiL4DuO757","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"name":"Smarty Bot","code":"smarty-bot","domain":"smartybot.xyz","sharedPorts":["8000"],"versions":[],"custom":true,"type":"web","main":true,"_id":"lltkpq6p347kystct","dockerhub_user":"trydirect","dockerhub_name":"smarty-bot","url_app":"smartybot.xyz","url_git":"https://github.com/vsilent/smarty.git","disk_size":"1Gb","ram_size":"1Gb","cpu":1}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\"><strong style=\"font-weight:bold\">Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)</strong></p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.6","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","versions":[{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}],"domain":"","sharedPorts":["9000"],"main":true,"version":{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}}],"service":[{"_etag":null,"_id":230,"_created":"2023-05-24T12:51:52.108972","_updated":"2023-08-04T12:18:34.670194","name":"pgrst","code":"pgrst","role":null,"type":"service","default":null,"popularity":null,"descr":null,"ports":null,"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":null,"category_id":null,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\">PostgREST description</p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"1","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"pgrst","versions":[{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"},{"_etag":null,"_id":563,"_created":null,"_updated":"2023-05-24T12:52:15.351522","app_id":230,"name":"0.0.5","version":"0.0.5","update_status":"ready_for_testing","tag":"0.0.5"}],"domain":"","sharedPorts":["9999"],"main":true,"version":{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"}}],"servers_count":3,"custom_stack_name":"mysampleproject","custom_stack_code":"another-bot","custom_stack_category":["New"],"custom_stack_short_description":"sample short description","custom_stack_description":"stack description","custom_stack_publish":false,"project_name":"Smarty Bot","project_git_url":"https://github.com/vsilent/smarty.git","project_overview":"my product 1","project_description":"my product 1"}}
diff --git a/tests/custom-stack-payload-4.json b/tests/custom-stack-payload-4.json
deleted file mode 100644
index 6f4bc7c4..00000000
--- a/tests/custom-stack-payload-4.json
+++ /dev/null
@@ -1 +0,0 @@
-{"commonDomain":"", "domainList":{}, "region":"fsn1", "zone":null, "server":"cx21", "os":"ubuntu-20.04", "ssl":"letsencrypt", "vars":[], "integrated_features":[],"extended_features":[],"subscriptions":["stack_migration"],"save_token":false,"cloud_token":"r6LAjqrynVt7pUwctVkzBlJmKjLOCxJIWjZFMLTkPYCCB4rsgphhEVhiL4DuO757","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"name":"Smarty Bot","code":"smarty-bot","domain":"smartybot.xyz","sharedPorts":["8000"],"versions":[],"custom":true,"type":"web","main":true,"_id":"lltkpq6p347kystct","dockerhub_user":"trydirect","dockerhub_name":"smarty-bot","url_app":"smartybot.xyz","url_git":"https://github.com/vsilent/smarty.git","disk_size":"1Gb","ram_size":"1Gb","cpu":1}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\"><strong style=\"font-weight:bold\">Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)</strong></p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.6","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","versions":[{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}],"domain":"","sharedPorts":["9000"],"main":true,"version":{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}}],"service":[{"_etag":null,"_id":230,"_created":"2023-05-24T12:51:52.108972","_updated":"2023-08-04T12:18:34.670194","name":"pgrst","code":"pgrst","role":null,"type":"service","default":null,"popularity":null,"descr":null,"ports":null,"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":null,"category_id":null,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\">PostgREST description</p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"1","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"pgrst","versions":[{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"},{"_etag":null,"_id":563,"_created":null,"_updated":"2023-05-24T12:52:15.351522","app_id":230,"name":"0.0.5","version":"0.0.5","update_status":"ready_for_testing","tag":"0.0.5"}],"domain":"","sharedPorts":["9999"],"main":true,"version":{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"}}],"servers_count":3,"custom_stack_name":"mysampleproject4","custom_stack_code":"another-bot4","custom_stack_category":["New"],"custom_stack_short_description":"sample short description","custom_stack_description":"stack description","custom_stack_publish":false,"project_name":"Smarty Bot","project_git_url":"https://github.com/vsilent/smarty.git","project_overview":"my product 1","project_description":"my product 1"}}
diff --git a/tests/custom-stack-payload-5.json b/tests/custom-stack-payload-5.json
deleted file mode 100644
index 70bda710..00000000
--- a/tests/custom-stack-payload-5.json
+++ /dev/null
@@ -1 +0,0 @@
-{"commonDomain":"test.app", "domainList":{}, "region":"fsn1", "zone":null, "server":"cx21", "os":"ubuntu-20.04", "ssl":"letsencrypt", "vars":[], "integrated_features":[],"extended_features":[],"subscriptions":["stack_migration"],"save_token":false,"cloud_token":"r6LAjqrynVt7pUwctVkzBlJmKjLOCxJIWjZFMLTkPYCCB4rsgphhEVhiL4DuO757","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"name":"Smarty Bot","code":"smarty-bot","domain":"smartybot.xyz","sharedPorts":["8000"],"versions":[],"custom":true,"type":"web","main":true,"_id":"lltkpq6p347kystct","dockerhub_user":"trydirect","dockerhub_name":"smarty-bot","url_app":"smartybot.xyz","url_git":"https://github.com/vsilent/smarty.git","disk_size":"1Gb","ram_size":"1Gb","cpu":1}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\"><strong style=\"font-weight:bold\">Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)</strong></p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0.6,"ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","versions":[{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}],"domain":"","sharedPorts":["9000"],"main":true,"version":{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}}],"service":[{"_etag":null,"_id":230,"_created":"2023-05-24T12:51:52.108972","_updated":"2023-08-04T12:18:34.670194","name":"pgrst","code":"pgrst","role":null,"type":"service","default":null,"popularity":null,"descr":null,"ports":null,"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":null,"category_id":null,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\">PostgREST description</p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":1,"ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"pgrst","versions":[{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"},{"_etag":null,"_id":563,"_created":null,"_updated":"2023-05-24T12:52:15.351522","app_id":230,"name":"0.0.5","version":"0.0.5","update_status":"ready_for_testing","tag":"0.0.5"}],"domain":"","sharedPorts":["9999"],"main":true,"version":{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"}}],"servers_count":3,"custom_stack_name":"mysampleproject5","custom_stack_code":"another-bot5","custom_stack_category":["New"],"custom_stack_short_description":"sample short description","custom_stack_description":"stack description","custom_stack_publish":false,"project_name":"Smarty Bot","project_git_url":"https://github.com/vsilent/smarty.git","project_overview":"my product 1","project_description":"my product 1"}}
diff --git a/tests/custom-stack-payload-6.json b/tests/custom-stack-payload-6.json
deleted file mode 100644
index 61dedb91..00000000
--- a/tests/custom-stack-payload-6.json
+++ /dev/null
@@ -1 +0,0 @@
-{"commonDomain":"test.app", "domainList":{}, "region":"fsn1", "zone":null, "server":"cx21", "os":"ubuntu-20.04", "ssl":"letsencrypt", "vars":[], "integrated_features":[],"extended_features":[],"subscriptions":["stack_migration"],"save_token":false,"cloud_token":"r6LAjqrynVt7pUwctVkzBlJmKjLOCxJIWjZFMLTkPYCCB4rsgphhEVhiL4DuO757","provider":"htz","stack_code":"another-bot8","selected_plan":"plan-individual-monthly","custom":{"web":[{"name":"Smarty Bot","code":"smarty-bot","domain":"smartybot.xyz","sharedPorts":["8000"],"versions":[],"custom":true,"type":"web","main":true,"_id":0,"dockerhub_user":"trydirect","dockerhub_name":"smarty-bot","url_app":"smartybot.xyz","url_git":"https://github.com/vsilent/smarty.git","disk_size":"1Gb","ram_size":"1Gb","cpu":1}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\"><strong style=\"font-weight:bold\">Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)</strong></p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0.6,"ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","versions":[{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}],"domain":"","sharedPorts":["9000"],"main":true,"version":{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}}],"service":[{"_etag":null,"_id":230,"_created":"2023-05-24T12:51:52.108972","_updated":"2023-08-04T12:18:34.670194","name":"pgrst","code":"pgrst","role":null,"type":"service","default":null,"popularity":null,"descr":null,"ports":null,"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":null,"category_id":null,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\">PostgREST description</p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":1,"ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"pgrst","versions":[{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"},{"_etag":null,"_id":563,"_created":null,"_updated":"2023-05-24T12:52:15.351522","app_id":230,"name":"0.0.5","version":"0.0.5","update_status":"ready_for_testing","tag":"0.0.5"}],"domain":"","sharedPorts":["9999"],"main":true,"version":{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"}}],"servers_count":3,"custom_stack_name":"mysampleproject6","custom_stack_code":"another-bot8","custom_stack_category":["New"],"custom_stack_short_description":"sample short description","custom_stack_description":"stack description","custom_stack_publish":false,"project_name":"Smarty Bot","project_git_url":"https://github.com/vsilent/smarty.git","project_overview":"my product 1","project_description":"my product 1"}}
diff --git a/tests/custom-stack-payload-7.json b/tests/custom-stack-payload-7.json
deleted file mode 100644
index 1fbfbbdf..00000000
--- a/tests/custom-stack-payload-7.json
+++ /dev/null
@@ -1 +0,0 @@
-{"commonDomain":"test.app", "domainList":{}, "region":"fsn1", "zone":null, "server":"cx21", "os":"ubuntu-20.04", "ssl":"letsencrypt", "vars":[], "integrated_features":[],"extended_features":[],"subscriptions":["stack_migration"],"save_token":false,"cloud_token":"r6LAjqrynVt7pUwctVkzBlJmKjLOCxJIWjZFMLTkPYCCB4rsgphhEVhiL4DuO757","provider":"htz","stack_code":"another-bot8","selected_plan":"plan-individual-monthly","custom":{"web":[{"name":"Smarty Bot","code":"smarty-bot","domain":"smartybot.xyz","sharedPorts":["8000"],"versions":[],"custom":true,"type":"web","main":true,"_id":0,"dockerhub_user":"trydirect","dockerhub_name":"smarty-bot","url_app":"smartybot.xyz","url_git":"https://github.com/vsilent/smarty.git","disk_size":"1Gb","ram_size":"1Gb","cpu":1}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\"><strong style=\"font-weight:bold\">Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)</strong></p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0.6,"ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","versions":[{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}],"domain":"","sharedPorts":["9000"],"main":true,"version":{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}}],"service":[{"_etag":null,"_id":230,"_created":"2023-05-24T12:51:52.108972","_updated":"2023-08-04T12:18:34.670194","name":"pgrst","code":"pgrst","role":null,"type":"service","default":null,"popularity":null,"descr":null,"ports":null,"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":null,"category_id":null,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\">PostgREST description</p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":1,"ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"pgrst","versions":[{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"},{"_etag":null,"_id":563,"_created":null,"_updated":"2023-05-24T12:52:15.351522","app_id":230,"name":"0.0.5","version":"0.0.5","update_status":"ready_for_testing","tag":"0.0.5"}],"domain":"","sharedPorts":["9999"],"main":true,"version":{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"}}],"servers_count":3,"custom_stack_name":"mysampleproject7","custom_stack_code":"another-bot9","custom_stack_category":["New"],"custom_stack_short_description":"sample short description","custom_stack_description":"stack description","custom_stack_publish":false,"project_name":"Smarty Bot","project_git_url":"https://github.com/vsilent/smarty.git","project_overview":"my product 1","project_description":"my product 1"}}
diff --git a/tests/custom-stack-payload-8.json b/tests/custom-stack-payload-8.json
deleted file mode 100644
index 7a55fc94..00000000
--- a/tests/custom-stack-payload-8.json
+++ /dev/null
@@ -1 +0,0 @@
-{"commonDomain":"","domainList":{},"region":"fsn1","zone":null,"server":"cpx11","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":false,"disk_type":"pd-standart","cloud_token":"****","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"_etag":null,"_id":233,"_created":"2023-07-19T06:38:57.608807","_updated":"2023-08-15T11:12:14.921797","name":"FastAPI","code":"fastapi","role":null,"type":"web","default":true,"popularity":null,"descr":null,"ports":{"public":["5050","8000","8080"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":2500,"height":2500,"image":"8d1ba06d-04e2-4523-879e-2846c86a10d8.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":null,"plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0.1,"ram_size":"0.2Gb","disk_size":"0.2Gb","dockerhub_image":"fastapi","form":null,"versions":[{"_etag":null,"_id":587,"_created":null,"_updated":"2023-07-20T12:51:20.321999","app_id":233,"name":"0.0.1","version":"0.0.1","update_status":"ready_for_testing","tag":"0.0.1"},{"_etag":null,"_id":590,"_created":null,"_updated":"2023-07-20T08:36:49.651219","app_id":233,"name":"0.0.1","version":"0.0.1","update_status":"ready_for_testing","tag":"latest"},{"_etag":null,"_id":589,"_created":null,"_updated":"2023-07-20T08:36:55.200575","app_id":233,"name":"0.0.1","version":"0.0.1","update_status":"ready_for_testing","tag":"unstable"},{"_etag":"591","_id":591,"_created":null,"_updated":"2023-08-15T08:17:40.226186","app_id":233,"name":"Fastapi","version":"0.100.0","update_status":"published","tag":"stable"}],"domain":"fastapi.test","sharedPorts":["8000"],"main":true,"version":{"_etag":"591","_id":591,"_created":null,"_updated":"2023-08-15T08:17:40.226186","app_id":233,"name":"Fastapi","version":"0.100.0","update_status":"published","tag":"stable"}}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\"><strong style=\"font-weight:bold\">Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)</strong></p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0.6,"ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","form":null,"versions":[{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-09-07T07:28:51.18965","app_id":198,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"}],"domain":"","sharedPorts":["9000"],"main":false,"version":{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-09-07T07:28:51.18965","app_id":198,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"}}],"service":[{"_etag":null,"_id":24,"_created":"2020-06-19T13:07:24.228389","_updated":"2023-08-08T10:34:13.4985","name":"PostgreSQL","code":"postgres","role":[],"type":"service","default":null,"popularity":null,"descr":null,"ports":null,"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":576,"height":594,"image":"fd23f54c-e250-4228-8d56-7e5d93ffb925.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":null,"plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0,"ram_size":null,"disk_size":null,"dockerhub_image":"postgres","form":null,"versions":[{"_etag":null,"_id":458,"_created":"2022-10-20T07:57:05.88997","_updated":"2023-04-05T07:24:39.637749","app_id":24,"name":"15","version":"15","update_status":"published","tag":"15"},{"_etag":null,"_id":288,"_created":"2022-10-20T07:56:16.160116","_updated":"2023-03-17T13:46:51.433539","app_id":24,"name":"10.22","version":"10.22","update_status":"published","tag":"10.22"},{"_etag":null,"_id":303,"_created":"2022-10-20T07:57:24.710286","_updated":"2023-03-17T13:46:51.433539","app_id":24,"name":"13.8","version":"13.8","update_status":"published","tag":"13.8"},{"_etag":null,"_id":266,"_created":"2022-10-20T07:56:32.360852","_updated":"2023-04-05T06:49:31.782132","app_id":24,"name":"11","version":"11","update_status":"published","tag":"11"},{"_etag":null,"_id":267,"_created":"2022-10-20T07:57:35.552085","_updated":"2023-03-17T13:46:51.433539","app_id":24,"name":"12.12","version":"12.12","update_status":"published","tag":"12.12"},{"_etag":null,"_id":38,"_created":"2020-06-19T13:07:24.258724","_updated":"2022-10-20T07:58:06.882602","app_id":24,"name":"14.5","version":"14.5","update_status":"published","tag":"14.5"},{"_etag":null,"_id":564,"_created":null,"_updated":"2023-05-24T12:55:57.894215","app_id":24,"name":"0.0.5","version":"0.0.5","update_status":"ready_for_testing","tag":"0.0.5"},{"_etag":null,"_id":596,"_created":null,"_updated":"2023-08-09T11:00:33.004267","app_id":24,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"}],"domain":"","sharedPorts":["6372"],"main":false,"version":{"_etag":null,"_id":596,"_created":null,"_updated":"2023-08-09T11:00:33.004267","app_id":24,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"}}],"servers_count":3,"project_name":"FastAPI example","custom_stack_code":"fastapi-example","project_git_url":"https://github.com/trydirect/fastapi.git"}}
diff --git a/tests/custom-stack-payload-9.json b/tests/custom-stack-payload-9.json
deleted file mode 100644
index 3bf473d6..00000000
--- a/tests/custom-stack-payload-9.json
+++ /dev/null
@@ -1 +0,0 @@
-{"commonDomain":"","domainList":{},"region":"fsn1","zone":null,"server":"cx21","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":["stack_migration"],"form_app":[],"save_token":true,"disk_type":"pd-standart","cloud_token":"D3dkDL4Qy1NpFezTl60V5RYfB5p0BSwLLoKVHhEqmJQ3jObSG77irP86e9YtCYVi","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"_etag":null,"_id":233,"_created":"2023-07-19T06:38:57.608807","_updated":"2023-08-15T11:12:14.921797","name":"FastAPI","code":"fastapi","role":null,"type":"web","default":true,"popularity":null,"descr":null,"ports":{"public":["5050","8000","8080"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":2500,"height":2500,"image":"8d1ba06d-04e2-4523-879e-2846c86a10d8.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":null,"plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0.1,"ram_size":"0.2Gb","disk_size":"0.2Gb","dockerhub_image":"fastapi","form":null,"versions":[{"_etag":null,"_id":587,"_created":null,"_updated":"2023-07-20T12:51:20.321999","app_id":233,"name":"0.0.1","version":"0.0.1","update_status":"ready_for_testing","tag":"0.0.1"},{"_etag":null,"_id":590,"_created":null,"_updated":"2023-07-20T08:36:49.651219","app_id":233,"name":"0.0.1","version":"0.0.1","update_status":"ready_for_testing","tag":"latest"},{"_etag":null,"_id":589,"_created":null,"_updated":"2023-07-20T08:36:55.200575","app_id":233,"name":"0.0.1","version":"0.0.1","update_status":"ready_for_testing","tag":"unstable"},{"_etag":"591","_id":591,"_created":null,"_updated":"2023-08-15T08:17:40.226186","app_id":233,"name":"Fastapi","version":"0.100.0","update_status":"published","tag":"stable"}],"domain":"fastapi.test","sharedPorts":["8000"],"main":true,"version":{"_etag":"591","_id":591,"_created":null,"_updated":"2023-08-15T08:17:40.226186","app_id":233,"name":"Fastapi","version":"0.100.0","update_status":"published","tag":"stable"}},{"name":"smarty bot","code":"smarty-bot","domain":"smarty-bot.fastapi.test","sharedPorts":["8080"],"versions":[],"custom":true,"type":"web","main":false,"_id":0,"dockerhub_user":"vsilent","dockerhub_name":"smarty","disk_size":"1Gb","ram_size":"1Gb","cpu":1}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\"><strong style=\"font-weight:bold\">Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)</strong></p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0.6,"ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","form":null,"versions":[{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-09-07T07:28:51.18965","app_id":198,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"}],"domain":"","sharedPorts":["9000"],"main":false,"version":{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-09-07T07:28:51.18965","app_id":198,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"}}],"service":[{"_etag":null,"_id":246,"_created":"2023-09-15T11:41:21.353013","_updated":"2023-09-15T11:45:40.307663","name":"Mailhog","code":"mailhog_service","role":["mailhog"],"type":"service","default":null,"popularity":null,"descr":null,"ports":{"public":["8025"],"private":["1025","8025"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":48,"height":48,"image":"9ed27fd5-4745-4fa1-86a6-e53557d700ff.png"},"dark":{"width":48,"height":48,"image":"0d7b38f8-d2ad-4068-a5bd-aa32c7e1958f.png"}},"category_id":null,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\">MailHog is an email testing tool for developers.</p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0,"ram_size":null,"disk_size":null,"dockerhub_image":null,"form":null,"versions":[{"_etag":null,"_id":625,"_created":null,"_updated":"2023-09-15T11:42:10.48131","app_id":246,"name":"latest","version":"latest","update_status":"ready_for_production","tag":"latest"}],"domain":"","sharedPorts":["8025"],"main":false,"version":{"_etag":null,"_id":625,"_created":null,"_updated":"2023-09-15T11:42:10.48131","app_id":246,"name":"latest","version":"latest","update_status":"ready_for_production","tag":"latest"}},{"_etag":null,"_id":24,"_created":"2020-06-19T13:07:24.228389","_updated":"2023-08-08T10:34:13.4985","name":"PostgreSQL","code":"postgres","role":[],"type":"service","default":null,"popularity":null,"descr":null,"ports":null,"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":576,"height":594,"image":"fd23f54c-e250-4228-8d56-7e5d93ffb925.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":null,"plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0,"ram_size":null,"disk_size":null,"dockerhub_image":"postgres","form":null,"versions":[{"_etag":null,"_id":458,"_created":"2022-10-20T07:57:05.88997","_updated":"2023-04-05T07:24:39.637749","app_id":24,"name":"15","version":"15","update_status":"published","tag":"15"},{"_etag":null,"_id":288,"_created":"2022-10-20T07:56:16.160116","_updated":"2023-03-17T13:46:51.433539","app_id":24,"name":"10.22","version":"10.22","update_status":"published","tag":"10.22"},{"_etag":null,"_id":303,"_created":"2022-10-20T07:57:24.710286","_updated":"2023-03-17T13:46:51.433539","app_id":24,"name":"13.8","version":"13.8","update_status":"published","tag":"13.8"},{"_etag":null,"_id":266,"_created":"2022-10-20T07:56:32.360852","_updated":"2023-04-05T06:49:31.782132","app_id":24,"name":"11","version":"11","update_status":"published","tag":"11"},{"_etag":null,"_id":267,"_created":"2022-10-20T07:57:35.552085","_updated":"2023-03-17T13:46:51.433539","app_id":24,"name":"12.12","version":"12.12","update_status":"published","tag":"12.12"},{"_etag":null,"_id":38,"_created":"2020-06-19T13:07:24.258724","_updated":"2022-10-20T07:58:06.882602","app_id":24,"name":"14.5","version":"14.5","update_status":"published","tag":"14.5"},{"_etag":null,"_id":564,"_created":null,"_updated":"2023-05-24T12:55:57.894215","app_id":24,"name":"0.0.5","version":"0.0.5","update_status":"ready_for_testing","tag":"0.0.5"},{"_etag":null,"_id":596,"_created":null,"_updated":"2023-08-09T11:00:33.004267","app_id":24,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"}],"domain":"","sharedPorts":["6372"],"main":false,"version":{"_etag":null,"_id":596,"_created":null,"_updated":"2023-08-09T11:00:33.004267","app_id":24,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"}}],"servers_count":3,"project_name":"FastAPI example 3","custom_stack_code":"fastapi-example-3","project_git_url":"https://github.com/trydirect/fastapi.git"}}
diff --git a/tests/custom-stack-payload-singleapp.json b/tests/custom-stack-payload-singleapp.json
deleted file mode 100644
index e1b39987..00000000
--- a/tests/custom-stack-payload-singleapp.json
+++ /dev/null
@@ -1 +0,0 @@
-{"commonDomain":"","domainList":{},"region":"fsn1","zone":null,"server":"cx11","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":[],"save_token":false,"cloud_token":"nUDKdUk0b6fUOcW6I4zhmdMfhH8kR4nJrxWjRPxrfqTJ9smOSoKB4qZpsYjS8As6","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-free-periodically","custom":{"web":[{"name":"Smarty Bot","code":"smarty-bot","domain":"smartybot.com","sharedPorts":["8000"],"versions":[],"custom":true,"type":"web","main":true,"_id":"lmg1mg6c1acxn9bs7","dockerhub_user":"vsilent","dockerhub_name":"smarty"}],"feature":[],"service":[],"servers_count":3,"project_name":"sample1","custom_stack_code":"sample1"}}
diff --git a/tests/custom-stack-payload.json b/tests/custom-stack-payload.json
index a9ca754c..574ab3cb 100644
--- a/tests/custom-stack-payload.json
+++ b/tests/custom-stack-payload.json
@@ -1,4 +1 @@
-{"commonDomain":"","domainList":{},"region":"fsn1","zone":null,"server":"cx21","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":["stack_migration","stack_health_monitoring","stack_security_monitoring"],"save_token":true,"cloud_token":"r6LAjqrynVt7pUwctVkzBlJmKjLOCxJIWjZFMLTkPYCCB4rsgphhEVhiL4DuO757","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"name":"smarty database","code":"smarty-database","domain":"smarty-db.example.com","sharedPorts":["6532"],"versions":[],"custom":true,"type":"feature","main":true,"_id":"lm0gdh732y2qrojfl","dockerhub_user":"trydirect","dockerhub_name":"smarty-db","ram_size":"1Gb","cpu":1,"disk_size":"1Gb"}],"feature":[{"_etag":null,"_id":235,"_created":"2023-08-11T07:07:12.123355","_updated":"2023-08-15T13:07:30.597485","name":"Nginx Proxy Manager","code":"nginx_proxy_manager","role":["nginx_proxy_manager"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["80","81","443"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":192,"height":192,"image":"205128e6-0303-4b62-b946-9810b61f3d04.png"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"<p class=\"content-editor__unstyled\">Nginx Proxy Manager is a user-friendly software application designed to effortlessly route traffic to your websites, whether they&#x27;re hosted at home or elsewhere. It comes equipped with free SSL capabilities, eliminating the need for extensive Nginx or Letsencrypt knowledge. This tool proves especially handy for simplifying SSL generation and seamlessly proxying your docker containers.</p>","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"1","ram_size":"1Gb","disk_size":"0.3Gb","dockerhub_image":"nginx-proxy-manager","versions":[{"_etag":"599","_id":599,"_created":"2023-08-11T10:23:33","_updated":"2023-08-11T10:23:34.420583","app_id":235,"name":"Nginx proxy manager","version":"2.10.4","update_status":"ready_for_testing","tag":"unstable"},{"_etag":"601","_id":601,"_created":null,"_updated":"2023-08-15T08:11:19.703882","app_id":235,"name":"Nginx proxy manager","version":"2.10.4","update_status":"published","tag":"stable"},{"_etag":null,"_id":600,"_created":null,"_updated":"2023-08-11T07:08:43.944998","app_id":235,"name":"Nginx proxy manager","version":"2.10.4","update_status":"ready_for_testing","tag":"latest"}],"domain":"","sharedPorts":["443"],"main":true}],"service":[{"_etag":null,"_id":24,"_created":"2020-06-19T13:07:24.228389","_updated":"2023-08-08T10:34:13.4985","name":"PostgreSQL","code":"postgres","role":[],"type":"service","default":null,"popularity":null,"descr":null,"ports":null,"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":576,"height":594,"image":"fd23f54c-e250-4228-8d56-7e5d93ffb925.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":null,"plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":null,"ram_size":null,"disk_size":null,"dockerhub_image":"postgres","versions":[{"_etag":null,"_id":458,"_created":"2022-10-20T07:57:05.88997","_updated":"2023-04-05T07:24:39.637749","app_id":24,"name":"15","version":"15","update_status":"published","tag":"15"},{"_etag":null,"_id":288,"_created":"2022-10-20T07:56:16.160116","_updated":"2023-03-17T13:46:51.433539","app_id":24,"name":"10.22","version":"10.22","update_status":"published","tag":"10.22"},{"_etag":null,"_id":303,"_created":"2022-10-20T07:57:24.710286","_updated":"2023-03-17T13:46:51.433539","app_id":24,"name":"13.8","version":"13.8","update_status":"published","tag":"13.8"},{"_etag":null,"_id":266,"_created":"2022-10-20T07:56:32.360852","_updated":"2023-04-05T06:49:31.782132","app_id":24,"name":"11","version":"11","update_status":"published","tag":"11"},{"_etag":null,"_id":267,"_created":"2022-10-20T07:57:35.552085","_updated":"2023-03-17T13:46:51.433539","app_id":24,"name":"12.12","version":"12.12","update_status":"published","tag":"12.12"},{"_etag":null,"_id":38,"_created":"2020-06-19T13:07:24.258724","_updated":"2022-10-20T07:58:06.882602","app_id":24,"name":"14.5","version":"14.5","update_status":"published","tag":"14.5"},{"_etag":null,"_id":564,"_created":null,"_updated":"2023-05-24T12:55:57.894215","app_id":24,"name":"0.0.5","version":"0.0.5","update_status":"ready_for_testing","tag":"0.0.5"},{"_etag":null,"_id":596,"_created":null,"_updated":"2023-08-09T11:00:33.004267","app_id":24,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"}],"domain":"","sharedPorts":["5432"],"main":true}],"servers_count":3,"custom_stack_name":"SMBO","custom_stack_code":"sample-stack","custom_stack_git_url":"https://github.com/vsilent/smbo.git","custom_stack_category":["New","Marketing Automation"],"custom_stack_short_description":"Should be what is my project about shortly","custom_stack_description":"what is my project about more detailed","project_name":"sample stack","project_overview":"my short description, stack to marketplace, keep my token","project_description":"my full description, stack to marketplace, keep my token"}}
-
-
-
+{"commonDomain":"","domainList":{},"region":"fsn1","zone":null,"server":"cx11","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":false,"disk_type":"pd-standart","cloud_token":"****************************","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"networks":["default_network"],"web":[{"name":"MyApp","code":"myapp","domain":"myapp.com","shared_ports":[{"host_port":"80","container_port":"8080"}],"versions":[],"custom":true,"type":"web","network":["default_network"],"restart":"always","timestamp":"2024-01-15T09:47:46.580Z","_id":"lreqrvv81ifrmng39","dockerhub_user":"trydirect","dockerhub_name":"nginx-waf","environment":[{"key":"SOMEENVVAR1","value":"SOMEENVVAR_VALUE1"},{"key":"SOMEENVVAR2","value":"SOMEENVVAR_VALUE2"}],"volumes":[]}],"feature":[],"service":[],"servers_count":3,"project_git_url":"info@optimum-web.com","project_name":"Sample2024","custom_stack_code":"sample2024"}}
\ No newline at end of file
diff --git a/tests/model_user_stack.rs b/tests/model_user_stack.rs
index 57e5f160..88a005b5 100644
--- a/tests/model_user_stack.rs
+++ b/tests/model_user_stack.rs
@@ -67,7 +67,8 @@ fn test_docker_image_only_name_other_empty() {
     let docker_image = DockerImage {
         dockerhub_user: Some("".to_string()),
         dockerhub_name: Some("mysql".to_string()),
-        dockerhub_image: Some("".to_string(),)
+        dockerhub_image: Some("".to_string(),),
+        dockerhub_password: None,
     };
     let output = docker_image.to_string();
     assert_eq!(String::from("mysql:latest"), output);
@@ -78,7 +79,8 @@ fn test_docker_image_only_name_other_none() {
     let docker_image = DockerImage {
         dockerhub_user: None,
         dockerhub_name: Some("mysql".to_string()),
-        dockerhub_image: None
+        dockerhub_image: None,
+        dockerhub_password: None,
     };
     let output = docker_image.to_string();
     assert_eq!(String::from("mysql:latest"), output);
@@ -88,7 +90,8 @@ fn test_docker_image_namespace_and_repo() {
     let docker_image = DockerImage {
         dockerhub_user: Some("trydirect".to_string()),
         dockerhub_name: Some("mysql".to_string()),
-        dockerhub_image: Some("".to_string(),)
+        dockerhub_image: Some("".to_string(),),
+        dockerhub_password: None,
     };
     let output = docker_image.to_string();
     assert_eq!(String::from("trydirect/mysql:latest"), output);
@@ -99,7 +102,8 @@ fn test_docker_image_namespace_and_repo_tag() {
     let docker_image = DockerImage {
         dockerhub_user: Some("trydirect".to_string()),
         dockerhub_name: Some("mysql:8.1".to_string()),
-        dockerhub_image: Some("".to_string(),)
+        dockerhub_image: Some("".to_string(),),
+        dockerhub_password: None,
     };
     let output = docker_image.to_string();
     assert_eq!(String::from("trydirect/mysql:8.1"), output);
@@ -109,7 +113,8 @@ fn test_docker_image_only_image() {
     let docker_image = DockerImage {
         dockerhub_user: None,
         dockerhub_name: None,
-        dockerhub_image: Some("trydirect/mysql:stable".to_string(),)
+        dockerhub_image: Some("trydirect/mysql:stable".to_string(),),
+        dockerhub_password: None,
     };
     let output = docker_image.to_string();
     assert_eq!(String::from("trydirect/mysql:stable"), output);
@@ -120,7 +125,8 @@ fn test_docker_image_only_image_other_empty() {
     let docker_image = DockerImage {
         dockerhub_user: Some("".to_string()),
         dockerhub_name: Some("".to_string()),
-        dockerhub_image: Some("trydirect/mysql:stable".to_string())
+        dockerhub_image: Some("trydirect/mysql:stable".to_string()),
+        dockerhub_password: None,
     };
     let output = docker_image.to_string();
     assert_eq!(String::from("trydirect/mysql:stable"), output);
@@ -130,7 +136,8 @@ fn test_docker_repo_name_with_tag_other_none() {
     let docker_image = DockerImage {
         dockerhub_user: None,
         dockerhub_name: Some("mysql:stable".to_string()),
-        dockerhub_image: None
+        dockerhub_image: None,
+        dockerhub_password: None,
     };
     let output = docker_image.to_string();
     assert_eq!(String::from("mysql:stable"), output);

From 6731fdf091e91e6488ae3e71cc107c19ea516d0a Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Mon, 15 Jan 2024 12:57:59 +0200
Subject: [PATCH 130/284] remove configuration.yaml from repo

---
 .gitignore                                      | 1 +
 configuration.yaml => configuration.yaml.backup | 0
 2 files changed, 1 insertion(+)
 rename configuration.yaml => configuration.yaml.backup (100%)

diff --git a/.gitignore b/.gitignore
index 32da6d82..547e8c8c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 target
 .idea/
 files
+configuration.yaml
diff --git a/configuration.yaml b/configuration.yaml.backup
similarity index 100%
rename from configuration.yaml
rename to configuration.yaml.backup

From 27b2eda43da9477dc8222faadd30dec4ddf72fae Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Mon, 15 Jan 2024 13:00:12 +0200
Subject: [PATCH 131/284] remove configuration.yaml from repo

---
 configuration.yaml.orig | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)
 create mode 100644 configuration.yaml.orig

diff --git a/configuration.yaml.orig b/configuration.yaml.orig
new file mode 100644
index 00000000..2e5d2968
--- /dev/null
+++ b/configuration.yaml.orig
@@ -0,0 +1,16 @@
+app_host: 127.0.0.1
+app_port: 8000
+auth_url: https://dev.try.direct/server/user/oauth_server/api/me
+max_clients_number: 2
+database:
+  host: 127.0.0.1
+  port: 5432
+  username: postgres
+  password: postgres
+  database_name: stacker
+
+amqp:
+  host: 51.15.74.139
+  port: 5672
+  username: guest
+  password: rabbitdev2023rabbitmqctl@@

From e00f4edac1ea504974f7fffbc2b53181b1a8f78b Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Mon, 15 Jan 2024 13:00:50 +0200
Subject: [PATCH 132/284] remove configuration.yaml from repo

---
 .gitignore                |  1 +
 configuration.yaml.backup | 16 ----------------
 2 files changed, 1 insertion(+), 16 deletions(-)
 delete mode 100644 configuration.yaml.backup

diff --git a/.gitignore b/.gitignore
index 547e8c8c..e84716bd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@ target
 .idea/
 files
 configuration.yaml
+configuration.yaml.backup
diff --git a/configuration.yaml.backup b/configuration.yaml.backup
deleted file mode 100644
index 2e5d2968..00000000
--- a/configuration.yaml.backup
+++ /dev/null
@@ -1,16 +0,0 @@
-app_host: 127.0.0.1
-app_port: 8000
-auth_url: https://dev.try.direct/server/user/oauth_server/api/me
-max_clients_number: 2
-database:
-  host: 127.0.0.1
-  port: 5432
-  username: postgres
-  password: postgres
-  database_name: stacker
-
-amqp:
-  host: 51.15.74.139
-  port: 5672
-  username: guest
-  password: rabbitdev2023rabbitmqctl@@

From 81647e797df179e82d64a644f930dc88b3fc1e5b Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Mon, 15 Jan 2024 13:03:34 +0200
Subject: [PATCH 133/284] remove configuration.yaml from repo

---
 .gitignore              |  1 +
 configuration.yaml.orig | 16 ----------------
 2 files changed, 1 insertion(+), 16 deletions(-)
 delete mode 100644 configuration.yaml.orig

diff --git a/.gitignore b/.gitignore
index e84716bd..5be6467b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@ target
 files
 configuration.yaml
 configuration.yaml.backup
+configuration.yaml.orig
diff --git a/configuration.yaml.orig b/configuration.yaml.orig
deleted file mode 100644
index 2e5d2968..00000000
--- a/configuration.yaml.orig
+++ /dev/null
@@ -1,16 +0,0 @@
-app_host: 127.0.0.1
-app_port: 8000
-auth_url: https://dev.try.direct/server/user/oauth_server/api/me
-max_clients_number: 2
-database:
-  host: 127.0.0.1
-  port: 5432
-  username: postgres
-  password: postgres
-  database_name: stacker
-
-amqp:
-  host: 51.15.74.139
-  port: 5672
-  username: guest
-  password: rabbitdev2023rabbitmqctl@@

From 9d4f4995395bb842ed3a9487c213078457773ce3 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Mon, 15 Jan 2024 18:05:42 +0200
Subject: [PATCH 134/284] issue-auth dctypes removed

---
 Cargo.lock                   |  13 +
 Cargo.toml                   |   1 +
 src/forms/stack/app.rs       |   2 +-
 src/forms/stack/custom.rs    |   2 +-
 src/helpers/stack/builder.rs |  20 +-
 src/helpers/stack/dctypes.rs | 836 -----------------------------------
 src/helpers/stack/mod.rs     |   1 -
 7 files changed, 20 insertions(+), 855 deletions(-)
 delete mode 100644 src/helpers/stack/dctypes.rs

diff --git a/Cargo.lock b/Cargo.lock
index 759526eb..aae6152c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1112,6 +1112,18 @@ version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
 
+[[package]]
+name = "docker-compose-types"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "608ffe949fbffae4034bdde4bfa224238736ecc9e1a362997245c7282f49fa60"
+dependencies = [
+ "derive_builder",
+ "indexmap 2.1.0",
+ "serde",
+ "serde_yaml",
+]
+
 [[package]]
 name = "dotenvy"
 version = "0.15.7"
@@ -3132,6 +3144,7 @@ dependencies = [
  "config",
  "deadpool-lapin",
  "derive_builder",
+ "docker-compose-types",
  "futures",
  "futures-lite 1.13.0",
  "futures-util",
diff --git a/Cargo.toml b/Cargo.toml
index 80d2070c..649ae237 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -55,6 +55,7 @@ clap = { version = "4.4.8", features = ["derive"] }
 brotli = "3.4.0"
 serde_path_to_error = "0.1.14"
 deadpool-lapin = "0.11.0"
+docker-compose-types = "0.7.0"
 
 [dependencies.sqlx]
 version = "0.6.3"
diff --git a/src/forms/stack/app.rs b/src/forms/stack/app.rs
index 71509262..f38a81f9 100644
--- a/src/forms/stack/app.rs
+++ b/src/forms/stack/app.rs
@@ -1,5 +1,5 @@
 use crate::forms;
-use crate::helpers::stack::dctypes;
+use docker_compose_types as dctypes;
 use indexmap::IndexMap;
 use serde_json::Value;
 use serde::{Deserialize, Serialize};
diff --git a/src/forms/stack/custom.rs b/src/forms/stack/custom.rs
index 34f5437b..29cb2cd6 100644
--- a/src/forms/stack/custom.rs
+++ b/src/forms/stack/custom.rs
@@ -3,7 +3,7 @@ use serde_json::Value;
 use serde_valid::Validate;
 use crate::forms;
 use indexmap::IndexMap;
-use crate::helpers::stack::dctypes;
+use docker_compose_types as dctypes;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Custom {
diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index 5a30c9f5..730c4e51 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -1,5 +1,5 @@
 use crate::forms;
-use crate::helpers::stack::dctypes::{
+use docker_compose_types::{
     AdvancedVolumes, Compose, ComposeNetwork, ComposeNetworkSettingDetails, ComposeNetworks,
     ComposeVolume, Entrypoint, Environment, MapOrEmpty, NetworkSettings, Networks, Port, Ports,
     PublishedPort, Service, Services, SingleValue, TopLevelVolumes, Volumes,
@@ -16,18 +16,6 @@ impl Default for Config {
     }
 }
 
-impl Default for Port {
-    fn default() -> Self {
-        Port {
-            target: 80,
-            host_ip: None,
-            published: None,
-            protocol: None,
-            mode: None,
-        }
-    }
-}
-
 /// A builder for constructing docker compose.
 #[derive(Clone, Debug)]
 pub struct DcBuilder {
@@ -120,12 +108,12 @@ impl TryFrom<&forms::stack::App> for Service {
             None => vec![]
         };
 
-        let volumes: Vec<AdvancedVolumes> = app //todo
+        let volumes: Vec<Volumes> = app 
             .volumes
             .clone()
             .unwrap_or_default()
             .into_iter()
-            .map(|x| x.try_into().unwrap())
+            .map(|x| Volumes::Advanced(x.try_into().unwrap())) //todo
             .collect();
 
         let mut envs = IndexMap::new();
@@ -141,7 +129,7 @@ impl TryFrom<&forms::stack::App> for Service {
         service.networks = networks;
         service.ports = Ports::Long(ports);
         service.restart = Some("always".to_owned());
-        service.volumes = Volumes::Advanced(volumes);
+        service.volumes = volumes;
         service.environment = Environment::KvPair(envs);
 
         Ok(service)
diff --git a/src/helpers/stack/dctypes.rs b/src/helpers/stack/dctypes.rs
deleted file mode 100644
index 22383934..00000000
--- a/src/helpers/stack/dctypes.rs
+++ /dev/null
@@ -1,836 +0,0 @@
-use derive_builder::*;
-#[cfg(feature = "indexmap")]
-use indexmap::IndexMap;
-use serde::{Deserialize, Serialize};
-use serde_yaml::Value;
-#[cfg(not(feature = "indexmap"))]
-use std::collections::HashMap;
-use std::convert::TryFrom;
-use std::fmt;
-use std::str::FromStr;
-
-#[allow(clippy::large_enum_variant)]
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
-#[serde(untagged)]
-pub enum ComposeFile {
-    V2Plus(Compose),
-    #[cfg(feature = "indexmap")]
-    V1(IndexMap<String, Service>),
-    #[cfg(not(feature = "indexmap"))]
-    V1(HashMap<String, Service>),
-    Single(SingleService),
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
-pub struct SingleService {
-    pub service: Service,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
-pub struct Compose {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub version: Option<String>,
-    #[serde(default, skip_serializing_if = "Services::is_empty")]
-    pub services: Services,
-    #[serde(default, skip_serializing_if = "TopLevelVolumes::is_empty")]
-    pub volumes: TopLevelVolumes,
-    #[serde(default, skip_serializing_if = "ComposeNetworks::is_empty")]
-    pub networks: ComposeNetworks,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub service: Option<Service>,
-    #[cfg(feature = "indexmap")]
-    #[serde(flatten, skip_serializing_if = "IndexMap::is_empty")]
-    pub extensions: IndexMap<Extension, Value>,
-    #[cfg(not(feature = "indexmap"))]
-    #[serde(flatten, skip_serializing_if = "HashMap::is_empty")]
-    pub extensions: HashMap<Extension, Value>,
-}
-
-#[derive(Builder, Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
-#[builder(setter(into), default)]
-pub struct Service {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub hostname: Option<String>,
-    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
-    pub privileged: bool,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub healthcheck: Option<Healthcheck>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub deploy: Option<Deploy>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub image: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub container_name: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none", rename = "build")]
-    pub build_: Option<BuildStep>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub pid: Option<String>,
-    #[serde(default, skip_serializing_if = "Ports::is_empty")]
-    pub ports: Ports,
-    #[serde(default, skip_serializing_if = "Environment::is_empty")]
-    pub environment: Environment,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub network_mode: Option<String>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub devices: Vec<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub restart: Option<String>,
-    #[serde(default, skip_serializing_if = "Labels::is_empty")]
-    pub labels: Labels,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub tmpfs: Option<Tmpfs>,
-    #[serde(default, skip_serializing_if = "Ulimits::is_empty")]
-    pub ulimits: Ulimits,
-    #[serde(default, skip_serializing_if = "Volumes::is_empty")]
-    pub volumes: Volumes,
-    #[serde(default, skip_serializing_if = "Networks::is_empty")]
-    pub networks: Networks,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub cap_add: Vec<String>,
-    #[serde(default, skip_serializing_if = "DependsOnOptions::is_empty")]
-    pub depends_on: DependsOnOptions,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub command: Option<Command>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub entrypoint: Option<Entrypoint>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub env_file: Option<EnvFile>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub stop_grace_period: Option<String>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub profiles: Vec<String>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub links: Vec<String>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub dns: Vec<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub ipc: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub net: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub stop_signal: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub user: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub working_dir: Option<String>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub expose: Vec<String>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub volumes_from: Vec<String>,
-    #[cfg(feature = "indexmap")]
-    #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
-    pub extends: IndexMap<String, String>,
-    #[cfg(not(feature = "indexmap"))]
-    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
-    pub extends: HashMap<String, String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub logging: Option<LoggingParameters>,
-    #[serde(default, skip_serializing_if = "is_zero")]
-    pub scale: i64,
-    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
-    pub init: bool,
-    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
-    pub stdin_open: bool,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub shm_size: Option<String>,
-    #[cfg(feature = "indexmap")]
-    #[serde(flatten, skip_serializing_if = "IndexMap::is_empty")]
-    pub extensions: IndexMap<Extension, Value>,
-    #[cfg(not(feature = "indexmap"))]
-    #[serde(flatten, skip_serializing_if = "HashMap::is_empty")]
-    pub extensions: HashMap<Extension, Value>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub extra_hosts: Vec<String>,
-    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
-    pub tty: bool,
-    #[serde(default, skip_serializing_if = "SysCtls::is_empty")]
-    pub sysctls: SysCtls,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub security_opt: Vec<String>,
-}
-
-impl Service {
-    pub fn image(&self) -> &str {
-        self.image.as_deref().unwrap_or_default()
-    }
-
-    pub fn network_mode(&self) -> &str {
-        self.network_mode.as_deref().unwrap_or_default()
-    }
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(untagged)]
-pub enum EnvFile {
-    Simple(String),
-    List(Vec<String>),
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
-#[serde(untagged)]
-pub enum DependsOnOptions {
-    Simple(Vec<String>),
-    #[cfg(feature = "indexmap")]
-    Conditional(IndexMap<String, DependsCondition>),
-    #[cfg(not(feature = "indexmap"))]
-    Conditional(HashMap<String, DependsCondition>),
-}
-
-impl Default for DependsOnOptions {
-    fn default() -> Self {
-        Self::Simple(Vec::new())
-    }
-}
-
-impl DependsOnOptions {
-    pub fn is_empty(&self) -> bool {
-        match self {
-            Self::Simple(v) => v.is_empty(),
-            Self::Conditional(m) => m.is_empty(),
-        }
-    }
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-pub struct DependsCondition {
-    pub condition: String,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
-pub struct LoggingParameters {
-    pub driver: String,
-    #[cfg(feature = "indexmap")]
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub options: Option<IndexMap<String, SingleValue>>,
-    #[cfg(not(feature = "indexmap"))]
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub options: Option<HashMap<String, SingleValue>>,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
-#[serde(untagged)]
-pub enum Ports {
-    Short(Vec<String>),
-    Long(Vec<Port>),
-}
-
-impl Default for Ports {
-    fn default() -> Self {
-        Self::Short(Vec::default())
-    }
-}
-
-impl Ports {
-    pub fn is_empty(&self) -> bool {
-        match self {
-            Self::Short(v) => v.is_empty(),
-            Self::Long(v) => v.is_empty(),
-        }
-    }
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
-pub struct Port {
-    pub target: u16,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub host_ip: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub published: Option<PublishedPort>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub protocol: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub mode: Option<String>,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
-#[serde(untagged)]
-pub enum PublishedPort {
-    Single(u16),
-    Range(String),
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
-#[serde(untagged)]
-pub enum Environment {
-    List(Vec<String>),
-    #[cfg(feature = "indexmap")]
-    KvPair(IndexMap<String, Option<SingleValue>>),
-    #[cfg(not(feature = "indexmap"))]
-    KvPair(HashMap<String, Option<SingleValue>>),
-}
-
-impl Default for Environment {
-    fn default() -> Self {
-        Self::List(Vec::new())
-    }
-}
-
-impl Environment {
-    pub fn is_empty(&self) -> bool {
-        match self {
-            Self::List(v) => v.is_empty(),
-            Self::KvPair(m) => m.is_empty(),
-        }
-    }
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default, Ord, PartialOrd)]
-#[serde(try_from = "String")]
-pub struct Extension(String);
-
-impl FromStr for Extension {
-    type Err = ExtensionParseError;
-
-    fn from_str(s: &str) -> Result<Self, Self::Err> {
-        let owned = s.to_owned();
-        Extension::try_from(owned)
-    }
-}
-
-impl TryFrom<String> for Extension {
-    type Error = ExtensionParseError;
-
-    fn try_from(s: String) -> Result<Self, Self::Error> {
-        if s.starts_with("x-") {
-            Ok(Self(s))
-        } else {
-            Err(ExtensionParseError(s))
-        }
-    }
-}
-
-/// The result of a failed TryFrom<String> conversion for [`Extension`]
-///
-/// Contains the string that was being converted
-#[derive(Clone, Debug, Eq, PartialEq, Hash)]
-pub struct ExtensionParseError(pub String);
-
-impl fmt::Display for ExtensionParseError {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "unknown attribute {:?}, extensions must start with 'x-' (see https://docs.docker.com/compose/compose-file/#extension)", self.0)
-    }
-}
-
-impl std::error::Error for ExtensionParseError {}
-
-#[cfg(feature = "indexmap")]
-#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
-pub struct Services(pub IndexMap<String, Option<Service>>);
-#[cfg(not(feature = "indexmap"))]
-#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
-pub struct Services(pub HashMap<String, Option<Service>>);
-
-impl Services {
-    pub fn is_empty(&self) -> bool {
-        self.0.is_empty()
-    }
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
-#[serde(untagged)]
-pub enum Labels {
-    List(Vec<String>),
-    #[cfg(feature = "indexmap")]
-    Map(IndexMap<String, String>),
-    #[cfg(not(feature = "indexmap"))]
-    Map(HashMap<String, String>),
-}
-
-impl Default for Labels {
-    fn default() -> Self {
-        Self::List(Vec::new())
-    }
-}
-
-impl Labels {
-    pub fn is_empty(&self) -> bool {
-        match self {
-            Self::List(v) => v.is_empty(),
-            Self::Map(m) => m.is_empty(),
-        }
-    }
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
-#[serde(untagged)]
-pub enum Tmpfs {
-    Simple(String),
-    List(Vec<String>),
-}
-
-#[cfg(feature = "indexmap")]
-#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
-pub struct Ulimits(pub IndexMap<String, Ulimit>);
-#[cfg(not(feature = "indexmap"))]
-#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
-pub struct Ulimits(pub HashMap<String, Ulimit>);
-
-impl Ulimits {
-    pub fn is_empty(&self) -> bool {
-        self.0.is_empty()
-    }
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
-#[serde(untagged)]
-pub enum Ulimit {
-    Single(i64),
-    SoftHard { soft: i64, hard: i64 },
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
-#[serde(untagged)]
-pub enum Networks {
-    Simple(Vec<String>),
-    Advanced(AdvancedNetworks),
-}
-
-impl Default for Networks {
-    fn default() -> Self {
-        Self::Simple(Vec::new())
-    }
-}
-
-impl Networks {
-    pub fn is_empty(&self) -> bool {
-        match self {
-            Self::Simple(n) => n.is_empty(),
-            Self::Advanced(n) => n.0.is_empty(),
-        }
-    }
-}
-
-#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
-#[serde(untagged)]
-pub enum BuildStep {
-    Simple(String),
-    Advanced(AdvancedBuildStep),
-}
-
-#[derive(Builder, Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Default)]
-#[serde(deny_unknown_fields)]
-#[builder(setter(into), default)]
-pub struct AdvancedBuildStep {
-    pub context: String,
-    #[serde(default, skip_serializing_if = "Option::is_none")]
-    pub dockerfile: Option<String>,
-    #[serde(default, skip_serializing_if = "Option::is_none")]
-    pub args: Option<BuildArgs>,
-    #[serde(default, skip_serializing_if = "Option::is_none")]
-    pub shm_size: Option<u64>,
-    #[serde(default, skip_serializing_if = "Option::is_none")]
-    pub target: Option<String>,
-    #[serde(default, skip_serializing_if = "Option::is_none")]
-    pub network: Option<String>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub cache_from: Vec<String>,
-    #[serde(default, skip_serializing_if = "Labels::is_empty")]
-    pub labels: Labels,
-}
-
-#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
-#[serde(untagged)]
-pub enum BuildArgs {
-    Simple(String),
-    List(Vec<String>),
-    #[cfg(feature = "indexmap")]
-    KvPair(IndexMap<String, String>),
-    #[cfg(not(feature = "indexmap"))]
-    KvPair(HashMap<String, String>),
-}
-
-#[cfg(feature = "indexmap")]
-#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
-pub struct AdvancedNetworks(pub IndexMap<String, MapOrEmpty<AdvancedNetworkSettings>>);
-#[cfg(not(feature = "indexmap"))]
-#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
-pub struct AdvancedNetworks(pub HashMap<String, MapOrEmpty<AdvancedNetworkSettings>>);
-
-#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(deny_unknown_fields)]
-pub struct AdvancedNetworkSettings {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub ipv4_address: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub ipv6_address: Option<String>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub aliases: Vec<String>,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
-#[serde(untagged)]
-pub enum SysCtls {
-    List(Vec<String>),
-    #[cfg(feature = "indexmap")]
-    Map(IndexMap<String, Option<SingleValue>>),
-    #[cfg(not(feature = "indexmap"))]
-    Map(HashMap<String, Option<SingleValue>>),
-}
-
-impl Default for SysCtls {
-    fn default() -> Self {
-        Self::List(Vec::new())
-    }
-}
-
-impl SysCtls {
-    pub fn is_empty(&self) -> bool {
-        match self {
-            Self::List(v) => v.is_empty(),
-            Self::Map(m) => m.is_empty(),
-        }
-    }
-}
-
-#[cfg(feature = "indexmap")]
-#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
-pub struct TopLevelVolumes(pub IndexMap<String, MapOrEmpty<ComposeVolume>>);
-#[cfg(not(feature = "indexmap"))]
-#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
-pub struct TopLevelVolumes(pub HashMap<String, MapOrEmpty<ComposeVolume>>);
-
-impl TopLevelVolumes {
-    pub fn is_empty(&self) -> bool {
-        self.0.is_empty()
-    }
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
-pub struct ComposeVolume {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub driver: Option<String>,
-    #[cfg(feature = "indexmap")]
-    #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
-    pub driver_opts: IndexMap<String, Option<SingleValue>>,
-    #[cfg(not(feature = "indexmap"))]
-    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
-    pub driver_opts: HashMap<String, Option<SingleValue>>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub external: Option<ExternalVolume>,
-    #[serde(default, skip_serializing_if = "Labels::is_empty")]
-    pub labels: Labels,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub name: Option<String>,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
-#[serde(untagged)]
-pub enum ExternalVolume {
-    Bool(bool),
-    Name { name: String },
-}
-
-#[cfg(feature = "indexmap")]
-#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
-pub struct ComposeNetworks(pub IndexMap<String, MapOrEmpty<NetworkSettings>>);
-
-#[cfg(not(feature = "indexmap"))]
-#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
-pub struct ComposeNetworks(pub HashMap<String, MapOrEmpty<NetworkSettings>>);
-
-impl ComposeNetworks {
-    pub fn is_empty(&self) -> bool {
-        self.0.is_empty()
-    }
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(untagged)]
-pub enum ComposeNetwork {
-    Detailed(ComposeNetworkSettingDetails),
-    Bool(bool),
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(deny_unknown_fields)]
-pub struct ComposeNetworkSettingDetails {
-    pub name: String,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(deny_unknown_fields)]
-pub struct ExternalNetworkSettingBool(bool);
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
-#[serde(deny_unknown_fields)]
-pub struct NetworkSettings {
-    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
-    pub attachable: bool,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub driver: Option<String>,
-    #[cfg(feature = "indexmap")]
-    #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
-    pub driver_opts: IndexMap<String, Option<SingleValue>>,
-    #[cfg(not(feature = "indexmap"))]
-    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
-    pub driver_opts: HashMap<String, Option<SingleValue>>,
-    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
-    pub enable_ipv6: bool,
-    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
-    pub internal: bool,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub external: Option<ComposeNetwork>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub ipam: Option<Ipam>,
-    #[serde(default, skip_serializing_if = "Labels::is_empty")]
-    pub labels: Labels,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub name: Option<String>,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(deny_unknown_fields)]
-pub struct Ipam {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub driver: Option<String>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub config: Vec<IpamConfig>,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(deny_unknown_fields)]
-pub struct IpamConfig {
-    pub subnet: String,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub gateway: Option<String>,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
-#[serde(deny_unknown_fields)]
-pub struct Deploy {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub mode: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub replicas: Option<i64>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub labels: Vec<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub update_config: Option<UpdateConfig>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub resources: Option<Resources>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub restart_policy: Option<RestartPolicy>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub placement: Option<Placement>,
-}
-
-fn is_zero(val: &i64) -> bool {
-    *val == 0
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(deny_unknown_fields)]
-pub struct Healthcheck {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub test: Option<HealthcheckTest>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub interval: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub timeout: Option<String>,
-    #[serde(default, skip_serializing_if = "is_zero")]
-    pub retries: i64,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub start_period: Option<String>,
-    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
-    pub disable: bool,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(untagged)]
-pub enum HealthcheckTest {
-    Single(String),
-    Multiple(Vec<String>),
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
-#[serde(deny_unknown_fields)]
-pub struct Limits {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub cpus: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub memory: Option<String>,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
-#[serde(deny_unknown_fields)]
-pub struct Placement {
-    #[serde(skip_serializing_if = "Vec::is_empty", default)]
-    pub constraints: Vec<String>,
-    #[serde(skip_serializing_if = "Vec::is_empty", default)]
-    pub preferences: Vec<Preferences>,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(deny_unknown_fields)]
-pub struct Preferences {
-    pub spread: String,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
-#[serde(deny_unknown_fields)]
-pub struct Resources {
-    pub limits: Option<Limits>,
-    pub reservations: Option<Limits>,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
-#[serde(deny_unknown_fields)]
-pub struct RestartPolicy {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub condition: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub delay: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub max_attempts: Option<i64>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub window: Option<String>,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
-#[serde(deny_unknown_fields)]
-pub struct UpdateConfig {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub parallelism: Option<i64>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub delay: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub failure_action: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub monitor: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub max_failure_ratio: Option<f64>,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(untagged)]
-pub enum Volumes {
-    Simple(Vec<String>),
-    Advanced(Vec<AdvancedVolumes>),
-}
-
-impl Default for Volumes {
-    fn default() -> Self {
-        Self::Simple(Vec::new())
-    }
-}
-
-impl Volumes {
-    pub fn is_empty(&self) -> bool {
-        match self {
-            Self::Simple(v) => v.is_empty(),
-            Self::Advanced(v) => v.is_empty(),
-        }
-    }
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(deny_unknown_fields)]
-pub struct AdvancedVolumes {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub source: Option<String>,
-    pub target: String,
-    #[serde(rename = "type")]
-    pub _type: String,
-    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
-    pub read_only: bool,
-    #[serde(default, skip_serializing_if = "Option::is_none")]
-    pub bind: Option<Bind>,
-    #[serde(default, skip_serializing_if = "Option::is_none")]
-    pub volume: Option<Volume>,
-    #[serde(default, skip_serializing_if = "Option::is_none")]
-    pub tmpfs: Option<TmpfsSettings>,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
-#[serde(deny_unknown_fields)]
-pub struct Bind {
-    pub propagation: String,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
-#[serde(deny_unknown_fields)]
-pub struct Volume {
-    pub nocopy: bool,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
-#[serde(deny_unknown_fields)]
-pub struct TmpfsSettings {
-    pub size: u64,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(untagged)]
-pub enum Command {
-    Simple(String),
-    Args(Vec<String>),
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(untagged)]
-pub enum Entrypoint {
-    Simple(String),
-    List(Vec<String>),
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, PartialOrd)]
-#[serde(untagged)]
-pub enum SingleValue {
-    String(String),
-    Bool(bool),
-    Unsigned(u64),
-    Signed(i64),
-    Float(f64),
-}
-
-impl fmt::Display for SingleValue {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            Self::String(s) => f.write_str(s),
-            Self::Bool(b) => write!(f, "{b}"),
-            Self::Unsigned(u) => write!(f, "{u}"),
-            Self::Signed(i) => write!(f, "{i}"),
-            Self::Float(fl) => write!(f, "{fl}"),
-        }
-    }
-}
-
-#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash)]
-#[serde(untagged)]
-pub enum MapOrEmpty<T> {
-    Map(T),
-    Empty,
-}
-
-impl<T> Default for MapOrEmpty<T> {
-    fn default() -> Self {
-        Self::Empty
-    }
-}
-
-impl<T> From<MapOrEmpty<T>> for Option<T> {
-    fn from(value: MapOrEmpty<T>) -> Self {
-        match value {
-            MapOrEmpty::Map(t) => Some(t),
-            MapOrEmpty::Empty => None,
-        }
-    }
-}
-
-impl<T> Serialize for MapOrEmpty<T>
-    where
-        T: Serialize,
-{
-    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
-        where
-            S: serde::Serializer,
-    {
-        match self {
-            Self::Map(t) => t.serialize(serializer),
-            Self::Empty => {
-                use serde::ser::SerializeMap;
-                serializer.serialize_map(None)?.end()
-            }
-        }
-    }
-}
diff --git a/src/helpers/stack/mod.rs b/src/helpers/stack/mod.rs
index 7da2d2ec..9b5530b5 100644
--- a/src/helpers/stack/mod.rs
+++ b/src/helpers/stack/mod.rs
@@ -1,2 +1 @@
 pub(crate) mod builder;
-pub(crate) mod dctypes;
\ No newline at end of file

From 362cb14bfb9be81769498a966e89c9cba69ee560 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Tue, 16 Jan 2024 18:16:36 +0200
Subject: [PATCH 135/284] issue-auth volumes

---
 src/helpers/stack/builder.rs | 21 ++++++++++++---------
 1 file changed, 12 insertions(+), 9 deletions(-)

diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index 730c4e51..7f2e57fe 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -96,7 +96,6 @@ impl TryFrom<&forms::stack::App> for Service {
         };
 
         let networks = Networks::try_from(&app.network).unwrap_or_default();
-
         let ports: Vec<Port> = match &app.ports {
             Some(ports) => {
                 let mut collector = vec![];
@@ -108,16 +107,20 @@ impl TryFrom<&forms::stack::App> for Service {
             None => vec![]
         };
 
-        let volumes: Vec<Volumes> = app 
-            .volumes
-            .clone()
-            .unwrap_or_default()
-            .into_iter()
-            .map(|x| Volumes::Advanced(x.try_into().unwrap())) //todo
-            .collect();
+        let volumes: Vec<Volumes> = match &app.volumes {
+            Some(volumes) => {
+                let mut collector = vec![];
+                for volume in volumes {
+                    collector.push(Volumes::Advanced(volume.clone().try_into()?));
+                }
+
+                collector
+            },
+            None => vec![]
+        };
 
         let mut envs = IndexMap::new();
-        for item in app.environment.environment.clone().unwrap_or_default() { //todo
+        for item in app.environment.environment.clone().unwrap_or_default() {
             let items = item
                 .into_iter()
                 .map(|(k, v)| (k, Some(SingleValue::String(v.clone()))))

From 3dfe453bc0fcb489ae6ce2370696e6fb228a31ea Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Tue, 16 Jan 2024 18:32:22 +0200
Subject: [PATCH 136/284] issue-auth forms::stack::volume

---
 src/forms/stack/mod.rs       | 34 +++++---------------------
 src/forms/stack/port.rs      | 33 +++++++++++++++++++++++++
 src/forms/stack/volume.rs    | 43 +++++++++++++++++++++++++++++++++
 src/forms/stack/volumes.rs   |  7 ++++++
 src/helpers/stack/builder.rs | 47 ------------------------------------
 5 files changed, 89 insertions(+), 75 deletions(-)
 create mode 100644 src/forms/stack/port.rs
 create mode 100644 src/forms/stack/volume.rs
 create mode 100644 src/forms/stack/volumes.rs

diff --git a/src/forms/stack/mod.rs b/src/forms/stack/mod.rs
index aadeddcc..2ea7aa04 100644
--- a/src/forms/stack/mod.rs
+++ b/src/forms/stack/mod.rs
@@ -8,12 +8,18 @@ use crate::forms;
 mod app;
 mod custom;
 mod form;
+mod port;
 mod payload;
+mod volumes;
+mod volume;
 
 pub use app::*;
 pub use custom::*;
 pub use form::*;
+pub use port::*;
 pub use payload::*;
+pub use volumes::*;
+pub use volume::*;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Role {
@@ -38,11 +44,6 @@ pub struct Requirements {
     pub ram_size: Option<String>,
 }
 
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct Port {
-    pub host_port: Option<String>,
-    pub container_port: Option<String>,
-}
 
 // #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 // pub struct Ports {
@@ -105,29 +106,6 @@ pub struct Network {
 pub struct Environment {
     pub(crate) environment: Option<Vec<HashMap<String, String>>>,
 }
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct Volume {
-    pub(crate) host_path: Option<String>,
-    pub(crate) container_path: Option<String>,
-}
-
-impl Volume {
-    pub fn is_named_docker(&self) -> bool {
-        // Docker named volumes typically don't contain special characters or slashes
-        // They are alphanumeric and may include underscores or hyphens
-        self
-            .host_path
-            .as_ref()
-            .unwrap()
-            .chars()
-            .all(|c| c.is_alphanumeric() || c == '_' || c == '-')
-    }
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct Volumes {
-    volumes: Vec<Volume>,
-}
 
 // pub(crate) type Networks = Option<Vec<String>>;
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
diff --git a/src/forms/stack/port.rs b/src/forms/stack/port.rs
new file mode 100644
index 00000000..9b96c72d
--- /dev/null
+++ b/src/forms/stack/port.rs
@@ -0,0 +1,33 @@
+use serde::{Deserialize, Serialize};
+use docker_compose_types as dctypes;
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct Port {
+    pub host_port: Option<String>,
+    pub container_port: Option<String>,
+}
+
+impl TryInto<dctypes::Port> for &Port {
+    type Error = String;
+    fn try_into(self) -> Result<dctypes::Port, Self::Error> {
+        let cp = self
+            .container_port
+            .as_ref()
+            .map_or(Ok(0u16), |s| s.parse::<u16>())
+            .map_err(|_| "Could not parse port".to_string())?;
+
+        let hp = self
+            .host_port
+            .as_ref()
+            .map_or(Ok(0u16), |s| s.parse::<u16>())
+            .map_err(|_| "Could not parse port".to_string())?;
+
+        Ok(dctypes::Port {
+            target: cp,
+            host_ip: None,
+            published: Some(dctypes::PublishedPort::Single(hp)),
+            protocol: None,
+            mode: None,
+        })
+    }
+}
diff --git a/src/forms/stack/volume.rs b/src/forms/stack/volume.rs
new file mode 100644
index 00000000..67263f08
--- /dev/null
+++ b/src/forms/stack/volume.rs
@@ -0,0 +1,43 @@
+use serde::{Deserialize, Serialize};
+use docker_compose_types as dctypes;
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct Volume {
+    pub(crate) host_path: Option<String>,
+    pub(crate) container_path: Option<String>,
+}
+
+impl Volume {
+    pub fn is_named_docker(&self) -> bool {
+        // Docker named volumes typically don't contain special characters or slashes
+        // They are alphanumeric and may include underscores or hyphens
+        self
+            .host_path
+            .as_ref()
+            .unwrap()
+            .chars()
+            .all(|c| c.is_alphanumeric() || c == '_' || c == '-')
+    }
+}
+
+impl TryInto<dctypes::AdvancedVolumes> for Volume { //todo reference
+    type Error = String;
+    fn try_into(self) -> Result<dctypes::AdvancedVolumes, Self::Error> {
+        let source = self.host_path.clone();
+        let target = self.container_path.clone();
+        tracing::debug!(
+            "Volume conversion result: source: {:?} target: {:?}",
+            source,
+            target
+        );
+        Ok(dctypes::AdvancedVolumes {
+            source: source,
+            target: target.unwrap_or("".to_string()),
+            _type: "".to_string(),
+            read_only: false,
+            bind: None,
+            volume: None,
+            tmpfs: None,
+        })
+    }
+}
diff --git a/src/forms/stack/volumes.rs b/src/forms/stack/volumes.rs
new file mode 100644
index 00000000..39ec33ea
--- /dev/null
+++ b/src/forms/stack/volumes.rs
@@ -0,0 +1,7 @@
+use serde::{Deserialize, Serialize};
+use crate::forms::stack::*;
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct Volumes {
+    volumes: Vec<Volume>,
+}
diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index 7f2e57fe..dd36f239 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -23,53 +23,6 @@ pub struct DcBuilder {
     pub(crate) stack: models::Stack,
 }
 
-impl TryInto<AdvancedVolumes> for forms::stack::Volume {
-    type Error = String;
-    fn try_into(self) -> Result<AdvancedVolumes, Self::Error> {
-        let source = self.host_path.clone();
-        let target = self.container_path.clone();
-        tracing::debug!(
-            "Volume conversion result: source: {:?} target: {:?}",
-            source,
-            target
-        );
-        Ok(AdvancedVolumes {
-            source: source,
-            target: target.unwrap_or("".to_string()),
-            _type: "".to_string(),
-            read_only: false,
-            bind: None,
-            volume: None,
-            tmpfs: None,
-        })
-    }
-}
-
-impl TryInto<Port> for &forms::stack::Port {
-    type Error = String;
-    fn try_into(self) -> Result<Port, Self::Error> {
-        let cp = self
-            .container_port
-            .as_ref()
-            .map_or(Ok(0u16), |s| s.parse::<u16>())
-            .map_err(|_| "Could not parse port".to_string())?;
-
-        let hp = self
-            .host_port
-            .as_ref()
-            .map_or(Ok(0u16), |s| s.parse::<u16>())
-            .map_err(|_| "Could not parse port".to_string())?;
-
-        Ok(Port {
-            target: cp,
-            host_ip: None,
-            published: Some(PublishedPort::Single(hp)),
-            protocol: None,
-            mode: None,
-        })
-    }
-}
-
 impl TryFrom<&forms::stack::ServiceNetworks> for Networks {
     type Error = ();
 

From 7f69bc8b9fcdf7789023d5e51f867d46282cd9e0 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Wed, 17 Jan 2024 13:50:48 +0200
Subject: [PATCH 137/284] container_port is required

---
 Cargo.lock                      | 576 ++++++++++++++++----------------
 Cargo.toml                      |   4 +-
 scripts/init_db.sh              |   3 +-
 src/forms/stack.rs              |  17 +-
 src/helpers/dockerhub.rs        |  22 +-
 src/helpers/stack/builder.rs    |   6 +-
 tests/custom-stack-payload.json |   2 +-
 7 files changed, 301 insertions(+), 329 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 56d7a9a2..6bc8dd89 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -21,9 +21,9 @@ dependencies = [
 
 [[package]]
 name = "actix-cors"
-version = "0.6.4"
+version = "0.6.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b340e9cfa5b08690aae90fb61beb44e9b06f44fe3d0f93781aaa58cfba86245e"
+checksum = "0346d8c1f762b41b458ed3145eea914966bb9ad20b9be0d6d463b20d45586370"
 dependencies = [
  "actix-utils",
  "actix-web",
@@ -36,16 +36,16 @@ dependencies = [
 
 [[package]]
 name = "actix-http"
-version = "3.4.0"
+version = "3.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a92ef85799cba03f76e4f7c10f533e66d87c9a7e7055f3391f09000ad8351bc9"
+checksum = "129d4c88e98860e1758c5de288d1632b07970a16d59bdf7b8d66053d582bb71f"
 dependencies = [
  "actix-codec",
  "actix-rt",
  "actix-service",
  "actix-utils",
- "ahash 0.8.6",
- "base64 0.21.5",
+ "ahash 0.8.7",
+ "base64 0.21.7",
  "bitflags 2.4.1",
  "brotli",
  "bytes",
@@ -80,14 +80,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb"
 dependencies = [
  "quote",
- "syn 2.0.39",
+ "syn 2.0.48",
 ]
 
 [[package]]
 name = "actix-router"
-version = "0.5.1"
+version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d66ff4d247d2b160861fa2866457e85706833527840e4133f8f49aa423a38799"
+checksum = "d22475596539443685426b6bdadb926ad0ecaefdfc5fb05e5e3441f15463c511"
 dependencies = [
  "bytestring",
  "http",
@@ -146,9 +146,9 @@ dependencies = [
 
 [[package]]
 name = "actix-web"
-version = "4.4.0"
+version = "4.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e4a5b5e29603ca8c94a77c65cf874718ceb60292c5a5c3e5f4ace041af462b9"
+checksum = "e43428f3bf11dee6d166b00ec2df4e3aa8cc1606aaa0b7433c146852e2f4e03b"
 dependencies = [
  "actix-codec",
  "actix-http",
@@ -159,7 +159,7 @@ dependencies = [
  "actix-service",
  "actix-utils",
  "actix-web-codegen",
- "ahash 0.8.6",
+ "ahash 0.8.7",
  "bytes",
  "bytestring",
  "cfg-if",
@@ -180,7 +180,7 @@ dependencies = [
  "serde_urlencoded",
  "smallvec",
  "socket2 0.5.5",
- "time 0.3.30",
+ "time 0.3.31",
  "url",
 ]
 
@@ -193,7 +193,7 @@ dependencies = [
  "actix-router",
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.48",
 ]
 
 [[package]]
@@ -204,7 +204,7 @@ checksum = "1d613edf08a42ccc6864c941d30fe14e1b676a77d16f1dbadc1174d065a0a775"
 dependencies = [
  "actix-utils",
  "actix-web",
- "base64 0.21.5",
+ "base64 0.21.7",
  "futures-core",
  "futures-util",
  "log",
@@ -232,19 +232,19 @@ version = "0.7.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd"
 dependencies = [
- "getrandom 0.2.11",
+ "getrandom 0.2.12",
  "once_cell",
  "version_check",
 ]
 
 [[package]]
 name = "ahash"
-version = "0.8.6"
+version = "0.8.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
+checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01"
 dependencies = [
  "cfg-if",
- "getrandom 0.2.11",
+ "getrandom 0.2.12",
  "once_cell",
  "version_check",
  "zerocopy",
@@ -345,9 +345,9 @@ dependencies = [
 
 [[package]]
 name = "anstream"
-version = "0.6.4"
+version = "0.6.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44"
+checksum = "4cd2405b3ac1faab2990b74d728624cd9fd115651fcecc7c2d8daf01376275ba"
 dependencies = [
  "anstyle",
  "anstyle-parse",
@@ -365,37 +365,37 @@ checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
 
 [[package]]
 name = "anstyle-parse"
-version = "0.2.2"
+version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140"
+checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
 dependencies = [
  "utf8parse",
 ]
 
 [[package]]
 name = "anstyle-query"
-version = "1.0.0"
+version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
+checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
 dependencies = [
- "windows-sys 0.48.0",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
 name = "anstyle-wincon"
-version = "3.0.1"
+version = "3.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
+checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
 dependencies = [
  "anstyle",
- "windows-sys 0.48.0",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
 name = "anyhow"
-version = "1.0.75"
+version = "1.0.79"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
+checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
 
 [[package]]
 name = "assert-json-diff"
@@ -425,7 +425,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c"
 dependencies = [
  "concurrent-queue",
- "event-listener 4.0.0",
+ "event-listener 4.0.3",
  "event-listener-strategy",
  "futures-core",
  "pin-project-lite",
@@ -437,26 +437,26 @@ version = "1.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c"
 dependencies = [
- "async-lock 3.1.2",
+ "async-lock 3.3.0",
  "async-task",
  "concurrent-queue",
  "fastrand 2.0.1",
- "futures-lite 2.0.1",
+ "futures-lite 2.2.0",
  "slab",
 ]
 
 [[package]]
 name = "async-global-executor"
-version = "2.4.0"
+version = "2.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b4353121d5644cdf2beb5726ab752e79a8db1ebb52031770ec47db31d245526"
+checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c"
 dependencies = [
  "async-channel 2.1.1",
  "async-executor",
- "async-io 2.2.1",
- "async-lock 3.1.2",
+ "async-io 2.3.0",
+ "async-lock 3.3.0",
  "blocking",
- "futures-lite 2.0.1",
+ "futures-lite 2.2.0",
  "once_cell",
 ]
 
@@ -493,18 +493,18 @@ dependencies = [
 
 [[package]]
 name = "async-io"
-version = "2.2.1"
+version = "2.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d6d3b15875ba253d1110c740755e246537483f152fa334f91abd7fe84c88b3ff"
+checksum = "fb41eb19024a91746eba0773aa5e16036045bbf45733766661099e182ea6a744"
 dependencies = [
- "async-lock 3.1.2",
+ "async-lock 3.3.0",
  "cfg-if",
  "concurrent-queue",
  "futures-io",
- "futures-lite 2.0.1",
+ "futures-lite 2.2.0",
  "parking",
- "polling 3.3.1",
- "rustix 0.38.25",
+ "polling 3.3.2",
+ "rustix 0.38.30",
  "slab",
  "tracing",
  "windows-sys 0.52.0",
@@ -521,11 +521,11 @@ dependencies = [
 
 [[package]]
 name = "async-lock"
-version = "3.1.2"
+version = "3.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dea8b3453dd7cc96711834b75400d671b73e3656975fa68d9f277163b7f7e316"
+checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b"
 dependencies = [
- "event-listener 4.0.0",
+ "event-listener 4.0.3",
  "event-listener-strategy",
  "pin-project-lite",
 ]
@@ -544,19 +544,19 @@ dependencies = [
 
 [[package]]
 name = "async-task"
-version = "4.5.0"
+version = "4.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4eb2cdb97421e01129ccb49169d8279ed21e829929144f4a22a6e54ac549ca1"
+checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799"
 
 [[package]]
 name = "async-trait"
-version = "0.1.74"
+version = "0.1.77"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9"
+checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.48",
 ]
 
 [[package]]
@@ -603,9 +603,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
 
 [[package]]
 name = "base64"
-version = "0.21.5"
+version = "0.21.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
+checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
 
 [[package]]
 name = "bitflags"
@@ -644,11 +644,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118"
 dependencies = [
  "async-channel 2.1.1",
- "async-lock 3.1.2",
+ "async-lock 3.3.0",
  "async-task",
  "fastrand 2.0.1",
  "futures-io",
- "futures-lite 2.0.1",
+ "futures-lite 2.2.0",
  "piper",
  "tracing",
 ]
@@ -754,9 +754,9 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "4.4.8"
+version = "4.4.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64"
+checksum = "80932e03c33999b9235edb8655bc9df3204adc9887c2f95b50cb1deb9fd54253"
 dependencies = [
  "clap_builder",
  "clap_derive",
@@ -764,14 +764,14 @@ dependencies = [
 
 [[package]]
 name = "clap_builder"
-version = "4.4.8"
+version = "4.4.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc"
+checksum = "d6c0db58c659eef1c73e444d298c27322a1b52f6927d2ad470c0c0f96fa7b8fa"
 dependencies = [
  "anstream",
  "anstyle",
  "clap_lex",
- "strsim",
+ "strsim 0.10.0",
 ]
 
 [[package]]
@@ -783,7 +783,7 @@ dependencies = [
  "heck",
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.48",
 ]
 
 [[package]]
@@ -800,9 +800,9 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
 
 [[package]]
 name = "concurrent-queue"
-version = "2.3.0"
+version = "2.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400"
+checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363"
 dependencies = [
  "crossbeam-utils",
 ]
@@ -839,7 +839,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
 dependencies = [
  "percent-encoding",
- "time 0.3.30",
+ "time 0.3.31",
  "version_check",
 ]
 
@@ -851,9 +851,9 @@ checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b"
 
 [[package]]
 name = "core-foundation"
-version = "0.9.3"
+version = "0.9.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
 dependencies = [
  "core-foundation-sys",
  "libc",
@@ -861,15 +861,15 @@ dependencies = [
 
 [[package]]
 name = "core-foundation-sys"
-version = "0.8.4"
+version = "0.8.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
+checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
 
 [[package]]
 name = "cpufeatures"
-version = "0.2.11"
+version = "0.2.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0"
+checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
 dependencies = [
  "libc",
 ]
@@ -900,22 +900,18 @@ dependencies = [
 
 [[package]]
 name = "crossbeam-queue"
-version = "0.3.8"
+version = "0.3.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add"
+checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35"
 dependencies = [
- "cfg-if",
  "crossbeam-utils",
 ]
 
 [[package]]
 name = "crossbeam-utils"
-version = "0.8.16"
+version = "0.8.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
-dependencies = [
- "cfg-if",
-]
+checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
 
 [[package]]
 name = "crypto-common"
@@ -947,7 +943,7 @@ dependencies = [
  "ident_case",
  "proc-macro2",
  "quote",
- "strsim",
+ "strsim 0.10.0",
  "syn 1.0.109",
 ]
 
@@ -983,9 +979,9 @@ checksum = "63dfa964fe2a66f3fde91fc70b267fe193d822c7e603e2a675a49a7f46ad3f49"
 
 [[package]]
 name = "deranged"
-version = "0.3.9"
+version = "0.3.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3"
+checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
 dependencies = [
  "powerfmt",
 ]
@@ -1118,12 +1114,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
 
 [[package]]
 name = "errno"
-version = "0.3.7"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8"
+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
 dependencies = [
  "libc",
- "windows-sys 0.48.0",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
@@ -1134,9 +1130,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
 
 [[package]]
 name = "event-listener"
-version = "4.0.0"
+version = "4.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "770d968249b5d99410d61f5bf89057f3199a077a04d087092f58e7d10692baae"
+checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e"
 dependencies = [
  "concurrent-queue",
  "parking",
@@ -1149,7 +1145,7 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3"
 dependencies = [
- "event-listener 4.0.0",
+ "event-listener 4.0.3",
  "pin-project-lite",
 ]
 
@@ -1246,9 +1242,9 @@ dependencies = [
 
 [[package]]
 name = "futures"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335"
+checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -1261,9 +1257,9 @@ dependencies = [
 
 [[package]]
 name = "futures-channel"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb"
+checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
 dependencies = [
  "futures-core",
  "futures-sink",
@@ -1271,15 +1267,15 @@ dependencies = [
 
 [[package]]
 name = "futures-core"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c"
+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
 
 [[package]]
 name = "futures-executor"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc"
+checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
 dependencies = [
  "futures-core",
  "futures-task",
@@ -1299,9 +1295,9 @@ dependencies = [
 
 [[package]]
 name = "futures-io"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa"
+checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
 
 [[package]]
 name = "futures-lite"
@@ -1320,40 +1316,39 @@ dependencies = [
 
 [[package]]
 name = "futures-lite"
-version = "2.0.1"
+version = "2.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3831c2651acb5177cbd83943f3d9c8912c5ad03c76afcc0e9511ba568ec5ebb"
+checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba"
 dependencies = [
  "fastrand 2.0.1",
  "futures-core",
  "futures-io",
- "memchr",
  "parking",
  "pin-project-lite",
 ]
 
 [[package]]
 name = "futures-macro"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb"
+checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.48",
 ]
 
 [[package]]
 name = "futures-sink"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817"
+checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
 
 [[package]]
 name = "futures-task"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2"
+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
 
 [[package]]
 name = "futures-timer"
@@ -1363,9 +1358,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
 
 [[package]]
 name = "futures-util"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104"
+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -1412,9 +1407,9 @@ dependencies = [
 
 [[package]]
 name = "getrandom"
-version = "0.2.11"
+version = "0.2.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
+checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
 dependencies = [
  "cfg-if",
  "libc",
@@ -1435,9 +1430,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
 
 [[package]]
 name = "h2"
-version = "0.3.22"
+version = "0.3.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178"
+checksum = "b553656127a00601c8ae5590fcfdc118e4083a7924b6cf4ffc1ea4b99dc429d7"
 dependencies = [
  "bytes",
  "fnv",
@@ -1463,11 +1458,11 @@ dependencies = [
 
 [[package]]
 name = "hashbrown"
-version = "0.14.2"
+version = "0.14.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156"
+checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
 dependencies = [
- "ahash 0.8.6",
+ "ahash 0.8.7",
  "allocator-api2",
 ]
 
@@ -1477,7 +1472,7 @@ version = "0.8.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
 dependencies = [
- "hashbrown 0.14.2",
+ "hashbrown 0.14.3",
 ]
 
 [[package]]
@@ -1503,9 +1498,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
 
 [[package]]
 name = "hkdf"
-version = "0.12.3"
+version = "0.12.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437"
+checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
 dependencies = [
  "hmac",
 ]
@@ -1532,9 +1527,9 @@ dependencies = [
 
 [[package]]
 name = "http-body"
-version = "0.4.5"
+version = "0.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
+checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
 dependencies = [
  "bytes",
  "http",
@@ -1576,9 +1571,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
 
 [[package]]
 name = "hyper"
-version = "0.14.27"
+version = "0.14.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468"
+checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80"
 dependencies = [
  "bytes",
  "futures-channel",
@@ -1591,7 +1586,7 @@ dependencies = [
  "httpdate",
  "itoa",
  "pin-project-lite",
- "socket2 0.4.10",
+ "socket2 0.5.5",
  "tokio",
  "tower-service",
  "tracing",
@@ -1613,9 +1608,9 @@ dependencies = [
 
 [[package]]
 name = "iana-time-zone"
-version = "0.1.58"
+version = "0.1.59"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20"
+checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539"
 dependencies = [
  "android_system_properties",
  "core-foundation-sys",
@@ -1658,7 +1653,6 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
 dependencies = [
  "autocfg",
  "hashbrown 0.12.3",
- "serde",
 ]
 
 [[package]]
@@ -1668,7 +1662,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
 dependencies = [
  "equivalent",
- "hashbrown 0.14.2",
+ "hashbrown 0.14.3",
  "serde",
 ]
 
@@ -1716,27 +1710,18 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
 
 [[package]]
 name = "itertools"
-version = "0.10.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
-dependencies = [
- "either",
-]
-
-[[package]]
-name = "itertools"
-version = "0.11.0"
+version = "0.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
+checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0"
 dependencies = [
  "either",
 ]
 
 [[package]]
 name = "itoa"
-version = "1.0.9"
+version = "1.0.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
+checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
 
 [[package]]
 name = "jobserver"
@@ -1749,9 +1734,9 @@ dependencies = [
 
 [[package]]
 name = "js-sys"
-version = "0.3.65"
+version = "0.3.67"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8"
+checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1"
 dependencies = [
  "wasm-bindgen",
 ]
@@ -1804,9 +1789,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 
 [[package]]
 name = "libc"
-version = "0.2.150"
+version = "0.2.152"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
+checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
 
 [[package]]
 name = "libredox"
@@ -1833,9 +1818,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
 
 [[package]]
 name = "linux-raw-sys"
-version = "0.4.11"
+version = "0.4.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
+checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
 
 [[package]]
 name = "local-channel"
@@ -1891,9 +1876,9 @@ dependencies = [
 
 [[package]]
 name = "memchr"
-version = "2.6.4"
+version = "2.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
+checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
 
 [[package]]
 name = "mime"
@@ -1918,9 +1903,9 @@ dependencies = [
 
 [[package]]
 name = "mio"
-version = "0.8.9"
+version = "0.8.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0"
+checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
 dependencies = [
  "libc",
  "log",
@@ -1993,24 +1978,24 @@ dependencies = [
 
 [[package]]
 name = "object"
-version = "0.32.1"
+version = "0.32.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
+checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
 dependencies = [
  "memchr",
 ]
 
 [[package]]
 name = "once_cell"
-version = "1.18.0"
+version = "1.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
 
 [[package]]
 name = "openssl"
-version = "0.10.60"
+version = "0.10.62"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800"
+checksum = "8cde4d2d9200ad5909f8dac647e29482e07c3a35de8a13fce7c9c7747ad9f671"
 dependencies = [
  "bitflags 2.4.1",
  "cfg-if",
@@ -2029,7 +2014,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.48",
 ]
 
 [[package]]
@@ -2040,9 +2025,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
 
 [[package]]
 name = "openssl-sys"
-version = "0.9.96"
+version = "0.9.98"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f"
+checksum = "c1665caf8ab2dc9aef43d1c0023bd904633a6a05cb30b0ad59bec2ae986e57a7"
 dependencies = [
  "cc",
  "libc",
@@ -2075,7 +2060,7 @@ dependencies = [
  "cbc",
  "cipher",
  "des",
- "getrandom 0.2.11",
+ "getrandom 0.2.12",
  "hmac",
  "lazy_static",
  "rc2",
@@ -2157,9 +2142,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
 
 [[package]]
 name = "pest"
-version = "2.7.5"
+version = "2.7.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae9cee2a55a544be8b89dc6848072af97a20f2422603c10865be2a42b580fff5"
+checksum = "1f200d8d83c44a45b21764d1916299752ca035d15ecd46faca3e9a2a2bf6ad06"
 dependencies = [
  "memchr",
  "thiserror",
@@ -2168,9 +2153,9 @@ dependencies = [
 
 [[package]]
 name = "pest_derive"
-version = "2.7.5"
+version = "2.7.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81d78524685f5ef2a3b3bd1cafbc9fcabb036253d9b1463e726a91cd16e2dfc2"
+checksum = "bcd6ab1236bbdb3a49027e920e693192ebfe8913f6d60e294de57463a493cfde"
 dependencies = [
  "pest",
  "pest_generator",
@@ -2178,22 +2163,22 @@ dependencies = [
 
 [[package]]
 name = "pest_generator"
-version = "2.7.5"
+version = "2.7.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68bd1206e71118b5356dae5ddc61c8b11e28b09ef6a31acbd15ea48a28e0c227"
+checksum = "2a31940305ffc96863a735bef7c7994a00b325a7138fdbc5bda0f1a0476d3275"
 dependencies = [
  "pest",
  "pest_meta",
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.48",
 ]
 
 [[package]]
 name = "pest_meta"
-version = "2.7.5"
+version = "2.7.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7c747191d4ad9e4a4ab9c8798f1e82a39affe7ef9648390b7e5548d18e099de6"
+checksum = "a7ff62f5259e53b78d1af898941cdcdccfae7385cf7d793a6e55de5d05bb4b7d"
 dependencies = [
  "once_cell",
  "pest",
@@ -2217,7 +2202,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.48",
 ]
 
 [[package]]
@@ -2257,9 +2242,9 @@ dependencies = [
 
 [[package]]
 name = "pkg-config"
-version = "0.3.27"
+version = "0.3.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
+checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a"
 
 [[package]]
 name = "polling"
@@ -2279,14 +2264,14 @@ dependencies = [
 
 [[package]]
 name = "polling"
-version = "3.3.1"
+version = "3.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf63fa624ab313c11656b4cda960bfc46c410187ad493c41f6ba2d8c1e991c9e"
+checksum = "545c980a3880efd47b2e262f6a4bb6daad6555cf3367aa9c4e52895f69537a41"
 dependencies = [
  "cfg-if",
  "concurrent-queue",
  "pin-project-lite",
- "rustix 0.38.25",
+ "rustix 0.38.30",
  "tracing",
  "windows-sys 0.52.0",
 ]
@@ -2329,18 +2314,18 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.70"
+version = "1.0.76"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
+checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "quote"
-version = "1.0.33"
+version = "1.0.35"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
 dependencies = [
  "proc-macro2",
 ]
@@ -2404,7 +2389,7 @@ version = "0.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
 dependencies = [
- "getrandom 0.2.11",
+ "getrandom 0.2.12",
 ]
 
 [[package]]
@@ -2460,7 +2445,7 @@ version = "0.4.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4"
 dependencies = [
- "getrandom 0.2.11",
+ "getrandom 0.2.12",
  "libredox",
  "thiserror",
 ]
@@ -2511,11 +2496,11 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
 
 [[package]]
 name = "reqwest"
-version = "0.11.22"
+version = "0.11.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b"
+checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41"
 dependencies = [
- "base64 0.21.5",
+ "base64 0.21.7",
  "bytes",
  "encoding_rs",
  "futures-core",
@@ -2570,12 +2555,12 @@ dependencies = [
 
 [[package]]
 name = "ring"
-version = "0.17.5"
+version = "0.17.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b"
+checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74"
 dependencies = [
  "cc",
- "getrandom 0.2.11",
+ "getrandom 0.2.12",
  "libc",
  "spin 0.9.8",
  "untrusted 0.9.0",
@@ -2634,15 +2619,15 @@ dependencies = [
 
 [[package]]
 name = "rustix"
-version = "0.38.25"
+version = "0.38.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e"
+checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca"
 dependencies = [
  "bitflags 2.4.1",
  "errno",
  "libc",
- "linux-raw-sys 0.4.11",
- "windows-sys 0.48.0",
+ "linux-raw-sys 0.4.12",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
@@ -2659,12 +2644,12 @@ dependencies = [
 
 [[package]]
 name = "rustls"
-version = "0.21.9"
+version = "0.21.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9"
+checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba"
 dependencies = [
  "log",
- "ring 0.17.5",
+ "ring 0.17.7",
  "rustls-webpki",
  "sct",
 ]
@@ -2676,7 +2661,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "25da151615461c7347114b1ad1a7458b4cdebc69cb220cd140cd5cb324b1dd37"
 dependencies = [
  "log",
- "rustls 0.21.9",
+ "rustls 0.21.10",
  "rustls-native-certs",
  "rustls-webpki",
 ]
@@ -2699,7 +2684,7 @@ version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
 dependencies = [
- "base64 0.21.5",
+ "base64 0.21.7",
 ]
 
 [[package]]
@@ -2708,23 +2693,23 @@ version = "0.101.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
 dependencies = [
- "ring 0.17.5",
+ "ring 0.17.7",
  "untrusted 0.9.0",
 ]
 
 [[package]]
 name = "ryu"
-version = "1.0.15"
+version = "1.0.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
+checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
 
 [[package]]
 name = "schannel"
-version = "0.1.22"
+version = "0.1.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88"
+checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534"
 dependencies = [
- "windows-sys 0.48.0",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
@@ -2739,7 +2724,7 @@ version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
 dependencies = [
- "ring 0.17.5",
+ "ring 0.17.7",
  "untrusted 0.9.0",
 ]
 
@@ -2768,35 +2753,35 @@ dependencies = [
 
 [[package]]
 name = "semver"
-version = "1.0.20"
+version = "1.0.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
+checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
 
 [[package]]
 name = "serde"
-version = "1.0.193"
+version = "1.0.195"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
+checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.193"
+version = "1.0.195"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
+checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.48",
 ]
 
 [[package]]
 name = "serde_json"
-version = "1.0.108"
+version = "1.0.111"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
+checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4"
 dependencies = [
  "itoa",
  "ryu",
@@ -2805,9 +2790,9 @@ dependencies = [
 
 [[package]]
 name = "serde_path_to_error"
-version = "0.1.14"
+version = "0.1.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335"
+checksum = "ebd154a240de39fdebcf5775d2675c204d7c13cf39a4c697be6493c8e734337c"
 dependencies = [
  "itoa",
  "serde",
@@ -2838,12 +2823,12 @@ dependencies = [
 
 [[package]]
 name = "serde_valid"
-version = "0.16.3"
+version = "0.17.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0adc7a19d45e581abc6d169c865a0b14b84bb43a9e966d1cca4d733e70f7f35a"
+checksum = "3ede56947572525b2c9640542edfa35bbc2f575fa03f97d4d68e9eb4e4c5136b"
 dependencies = [
- "indexmap 1.9.3",
- "itertools 0.10.5",
+ "indexmap 2.1.0",
+ "itertools",
  "num-traits",
  "once_cell",
  "paste",
@@ -2858,23 +2843,23 @@ dependencies = [
 
 [[package]]
 name = "serde_valid_derive"
-version = "0.16.3"
+version = "0.17.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "071237362e267e2a76ffe4434094e089dcd8b5e9d8423ada499e5550dcb0181d"
+checksum = "79ffdd7f3a5dea22dc86747bc02f5292a091977383c0124e7d77fc4fd120c486"
 dependencies = [
  "paste",
  "proc-macro-error",
  "proc-macro2",
  "quote",
- "strsim",
- "syn 1.0.109",
+ "strsim 0.11.0",
+ "syn 2.0.48",
 ]
 
 [[package]]
 name = "serde_valid_literal"
-version = "0.16.3"
+version = "0.17.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f57df292b1d64449f90794fc7a67efca0b21acca91493e64a46418a29bbe36b4"
+checksum = "be811ca7379637055f6474a1c0d0ed985c86966e580279ec980f77816b8f41b0"
 dependencies = [
  "paste",
  "regex",
@@ -2882,9 +2867,9 @@ dependencies = [
 
 [[package]]
 name = "serde_yaml"
-version = "0.9.27"
+version = "0.9.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c"
+checksum = "b1bf28c79a99f70ee1f1d83d10c875d2e70618417fda01ad1785e027579d9d38"
 dependencies = [
  "indexmap 2.1.0",
  "itoa",
@@ -2944,9 +2929,9 @@ dependencies = [
 
 [[package]]
 name = "smallvec"
-version = "1.11.2"
+version = "1.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
+checksum = "2593d31f82ead8df961d8bd23a64c2ccf2eb5dd34b0a34bfb4dd54011c72009e"
 
 [[package]]
 name = "socket2"
@@ -2985,11 +2970,11 @@ dependencies = [
 
 [[package]]
 name = "sqlformat"
-version = "0.2.2"
+version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6b7b278788e7be4d0d29c0f39497a0eef3fba6bbc8e70d8bf7fde46edeaa9e85"
+checksum = "ce81b7bd7c4493975347ef60d8c7e8b742d4694f4c49f93e0a12ea263938176c"
 dependencies = [
- "itertools 0.11.0",
+ "itertools",
  "nom",
  "unicode_categories",
 ]
@@ -3152,6 +3137,12 @@ version = "0.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
 
+[[package]]
+name = "strsim"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
+
 [[package]]
 name = "subtle"
 version = "2.5.0"
@@ -3171,9 +3162,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.39"
+version = "2.0.48"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
+checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -3215,35 +3206,35 @@ dependencies = [
 
 [[package]]
 name = "tempfile"
-version = "3.8.1"
+version = "3.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
+checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa"
 dependencies = [
  "cfg-if",
  "fastrand 2.0.1",
  "redox_syscall 0.4.1",
- "rustix 0.38.25",
- "windows-sys 0.48.0",
+ "rustix 0.38.30",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
 name = "thiserror"
-version = "1.0.50"
+version = "1.0.56"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
+checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.50"
+version = "1.0.56"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
+checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.48",
 ]
 
 [[package]]
@@ -3269,9 +3260,9 @@ dependencies = [
 
 [[package]]
 name = "time"
-version = "0.3.30"
+version = "0.3.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
+checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e"
 dependencies = [
  "deranged",
  "itoa",
@@ -3289,9 +3280,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
 
 [[package]]
 name = "time-macros"
-version = "0.2.15"
+version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20"
+checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f"
 dependencies = [
  "time-core",
 ]
@@ -3313,9 +3304,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
 
 [[package]]
 name = "tokio"
-version = "1.34.0"
+version = "1.35.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9"
+checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
 dependencies = [
  "backtrace",
  "bytes",
@@ -3338,7 +3329,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.48",
 ]
 
 [[package]]
@@ -3435,7 +3426,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.48",
 ]
 
 [[package]]
@@ -3444,12 +3435,12 @@ version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b5c266b9ac83dedf0e0385ad78514949e6d89491269e7065bee51d2bb8ec7373"
 dependencies = [
- "ahash 0.8.6",
+ "ahash 0.8.7",
  "gethostname",
  "log",
  "serde",
  "serde_json",
- "time 0.3.30",
+ "time 0.3.31",
  "tracing",
  "tracing-core",
  "tracing-log 0.1.4",
@@ -3508,9 +3499,9 @@ dependencies = [
 
 [[package]]
 name = "try-lock"
-version = "0.2.4"
+version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
 
 [[package]]
 name = "typenum"
@@ -3526,9 +3517,9 @@ checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
 
 [[package]]
 name = "unicode-bidi"
-version = "0.3.13"
+version = "0.3.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
+checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416"
 
 [[package]]
 name = "unicode-ident"
@@ -3559,9 +3550,9 @@ checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
 
 [[package]]
 name = "unsafe-libyaml"
-version = "0.2.9"
+version = "0.2.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa"
+checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b"
 
 [[package]]
 name = "untrusted"
@@ -3599,7 +3590,7 @@ version = "1.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560"
 dependencies = [
- "getrandom 0.2.11",
+ "getrandom 0.2.12",
  "serde",
 ]
 
@@ -3656,9 +3647,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.88"
+version = "0.2.90"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce"
+checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406"
 dependencies = [
  "cfg-if",
  "wasm-bindgen-macro",
@@ -3666,24 +3657,24 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.88"
+version = "0.2.90"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217"
+checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd"
 dependencies = [
  "bumpalo",
  "log",
  "once_cell",
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.48",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-futures"
-version = "0.4.38"
+version = "0.4.40"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02"
+checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461"
 dependencies = [
  "cfg-if",
  "js-sys",
@@ -3693,9 +3684,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.88"
+version = "0.2.90"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2"
+checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -3703,28 +3694,28 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.88"
+version = "0.2.90"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907"
+checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.48",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.88"
+version = "0.2.90"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b"
+checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b"
 
 [[package]]
 name = "web-sys"
-version = "0.3.65"
+version = "0.3.67"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85"
+checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
@@ -3736,7 +3727,7 @@ version = "0.22.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53"
 dependencies = [
- "ring 0.17.5",
+ "ring 0.17.7",
  "untrusted 0.9.0",
 ]
 
@@ -3783,11 +3774,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
 [[package]]
 name = "windows-core"
-version = "0.51.1"
+version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
 dependencies = [
- "windows-targets 0.48.5",
+ "windows-targets 0.52.0",
 ]
 
 [[package]]
@@ -3940,7 +3931,7 @@ checksum = "13a3a53eaf34f390dd30d7b1b078287dd05df2aa2e21a589ccb80f5c7253c2e9"
 dependencies = [
  "assert-json-diff",
  "async-trait",
- "base64 0.21.5",
+ "base64 0.21.7",
  "deadpool",
  "futures",
  "futures-timer",
@@ -3971,40 +3962,39 @@ checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
 
 [[package]]
 name = "zerocopy"
-version = "0.7.26"
+version = "0.7.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e97e415490559a91254a2979b4829267a57d2fcd741a98eee8b722fb57289aa0"
+checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
 dependencies = [
  "zerocopy-derive",
 ]
 
 [[package]]
 name = "zerocopy-derive"
-version = "0.7.26"
+version = "0.7.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd7e48ccf166952882ca8bd778a43502c64f33bf94c12ebe2a7f08e5a0f6689f"
+checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.48",
 ]
 
 [[package]]
 name = "zstd"
-version = "0.12.4"
+version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a27595e173641171fc74a1232b7b1c7a7cb6e18222c11e9dfb9888fa424c53c"
+checksum = "bffb3309596d527cfcba7dfc6ed6052f1d39dfbd7c867aa2e865e4a449c10110"
 dependencies = [
  "zstd-safe",
 ]
 
 [[package]]
 name = "zstd-safe"
-version = "6.0.6"
+version = "7.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee98ffd0b48ee95e6c5168188e44a54550b1564d9d530ee21d5f0eaed1069581"
+checksum = "43747c7422e2924c11144d5229878b98180ef8b06cca4ab5af37afc8a8d8ea3e"
 dependencies = [
- "libc",
  "zstd-sys",
 ]
 
diff --git a/Cargo.toml b/Cargo.toml
index bdd3f1bc..75df059a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -21,7 +21,7 @@ name = "console"
 actix-web = "4.3.1"
 chrono = { version = "0.4.26", features = ["time", "serde"] }
 config = "0.13.3"
-reqwest = { version = "0.11.17", features = ["json"] }
+reqwest = { version = "0.11.17", features = ["json", "blocking"] }
 serde = { version = "1.0.162", features = ["derive"] }
 tokio = { version = "1.28.1", features = ["full"] }
 tracing = { version = "0.1.37", features = ["log"] }
@@ -30,7 +30,7 @@ tracing-log = "0.1.3"
 tracing-subscriber = { version = "0.3.17", features = ["registry", "env-filter"] }
 uuid = { version = "1.3.4", features = ["v4", "serde"] }
 thiserror = "1.0"
-serde_valid = "0.16.3"
+serde_valid = {  version = "0.17.0", features = [] }
 serde_json = { version = "1.0.105", features = [] }
 serde_derive = "1.0.188"
 actix-web-httpauth = "0.8.1"
diff --git a/scripts/init_db.sh b/scripts/init_db.sh
index 8d844038..06693cdb 100755
--- a/scripts/init_db.sh
+++ b/scripts/init_db.sh
@@ -38,4 +38,5 @@ export DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@localhost:${DB_PORT}/${
 sqlx database create
 sqlx migrate run
 
->&2 echo "Postgres has been migrated, ready to go!"
\ No newline at end of file
+>&2 echo "Postgres has been migrated, ready to go!"
+
diff --git a/src/forms/stack.rs b/src/forms/stack.rs
index 205aae36..16c31647 100644
--- a/src/forms/stack.rs
+++ b/src/forms/stack.rs
@@ -1,7 +1,6 @@
 use serde::{Deserialize, Serialize};
 use serde_json::Value;
 use serde_valid::Validate;
-use std::collections::HashMap;
 use std::fmt;
 use crate::helpers::{login, docker_image_exists};
 use tokio::runtime::Runtime;
@@ -34,21 +33,23 @@ pub struct Port {
     #[validate(pattern = r"^\d+$")]
     pub host_port: Option<String>,
     #[validate(pattern = r"^\d+$")]
-    pub container_port: Option<String>,
+    pub container_port: String,
     #[validate(enumerate("tcp", "udp"))]
     pub protocol: Option<String>
 }
 
-fn validate_dockerhub_image(docker_image: DockerImage) -> Result<(), serde_valid::validation::Error> {
+fn validate_dockerhub_image(dockerhub: &DockerImage) -> Result<(), serde_valid::validation::Error> {
 
+    println!("validate dockerhub image {:?}", dockerhub);
+    tracing::debug!("Validate image at hub.docker.com...");
     // Create the runtime
     let rt = Runtime::new().unwrap();
 
     // Spawn a blocking function onto the runtime
     rt.block_on(async {
         let result = login(
-            docker_image.dockerhub_user.clone().unwrap_or("".to_string()).as_ref(),
-            docker_image.dockerhub_password.clone().unwrap_or("".to_string()).as_ref()
+            dockerhub.dockerhub_user.clone().unwrap_or("".to_string()).as_ref(),
+            dockerhub.dockerhub_password.clone().unwrap_or("".to_string()).as_ref()
         )
             .await
             .map_err(|err| serde_valid::validation::Error::Custom(format!("{:?}", err)))?;
@@ -63,8 +64,8 @@ fn validate_dockerhub_image(docker_image: DockerImage) -> Result<(), serde_valid
 
                 tracing::debug!("We were able to login hub.docker.com!");
                 docker_image_exists(
-                    docker_image.dockerhub_user.clone().unwrap().as_str(),
-                    docker_image.dockerhub_name.clone().unwrap().as_str(), tok)
+                    dockerhub.dockerhub_user.clone().unwrap().as_str(),
+                    dockerhub.dockerhub_name.clone().unwrap().as_str(), tok)
                     .await
                     .map_err(|err| serde_valid::validation::Error::Custom("Not exists".to_string()))
                     .map(|_| ())
@@ -73,7 +74,7 @@ fn validate_dockerhub_image(docker_image: DockerImage) -> Result<(), serde_valid
     })
 }
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-#[validate(custom(|docker_image| validate_dockerhub_image(docker_image)))]
+#[validate(custom(|dockerhub|validate_dockerhub_image(dockerhub)))]
 pub struct DockerImage {
     #[validate(min_length = 3)]
     #[validate(max_length = 50)]
diff --git a/src/helpers/dockerhub.rs b/src/helpers/dockerhub.rs
index e766b16a..108b4387 100644
--- a/src/helpers/dockerhub.rs
+++ b/src/helpers/dockerhub.rs
@@ -3,25 +3,6 @@ use serde_valid::Validate;
 // use tracing_subscriber::fmt::format;
 use serde_json::Value;
 
-// #[tokio::main]
-// async fn main() -> Result<(), String> {
-    // tokio::select! {
-    //     Ok(true) = docker_image_exist()  => {
-    //         println!("first branch. image exists.");
-    //     }
-    //     Ok(true) = docker_image_exist()  => {
-    //         println!("second branch. image exists.");
-    //     }
-    //     Ok(true) = docker_image_exist()  => {
-    //         println!("third branch. image exists.");
-    //     }
-    //     else => {
-    //         println!("else branch. image does not exists");
-    //     }
-    // };
-//     Ok(())
-// }
-
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct DockerHubToken {
@@ -116,9 +97,8 @@ pub async fn docker_image_exists(user: &str, repo: &str, token: String) -> Resul
         let active = tags.results
             .into_iter()
             .any(|tag| tag.tag_status.contains("active") );
-        // println!("is active {:?}", active);
         Ok(active)
     } else {
-        Err(String::from("There was no active images found in this repository"))
+        Err(String::from("There were no active tags found in this repository"))
     }
 }
diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index 77b648c1..84854d10 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -58,13 +58,13 @@ impl TryInto<Port> for stack::Port {
     type Error = String;
     fn try_into(self) -> Result<Port, Self::Error> {
         let cp  = self.container_port.clone()
-            .unwrap_or("".to_string())
-            .parse::<u16>().map_err(|err| "Could not parse port".to_string() )?;
+            .parse::<u16>().map_err(|err| "Could not parse container port".to_string() )?;
         let hp = self.host_port.clone()
             .unwrap_or("".to_string())
-            .parse::<u16>().map_err(|err| "Could not parse port".to_string() )?;
+            .parse::<u16>().map_err(|err| "Could not parse host port".to_string() )?;
 
         tracing::debug!("Port conversion result: cp: {:?} hp: {:?}", cp, hp);
+
         Ok(Port {
             target: cp,
             host_ip: None,
diff --git a/tests/custom-stack-payload.json b/tests/custom-stack-payload.json
index 574ab3cb..d699f892 100644
--- a/tests/custom-stack-payload.json
+++ b/tests/custom-stack-payload.json
@@ -1 +1 @@
-{"commonDomain":"","domainList":{},"region":"fsn1","zone":null,"server":"cx11","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":false,"disk_type":"pd-standart","cloud_token":"****************************","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"networks":["default_network"],"web":[{"name":"MyApp","code":"myapp","domain":"myapp.com","shared_ports":[{"host_port":"80","container_port":"8080"}],"versions":[],"custom":true,"type":"web","network":["default_network"],"restart":"always","timestamp":"2024-01-15T09:47:46.580Z","_id":"lreqrvv81ifrmng39","dockerhub_user":"trydirect","dockerhub_name":"nginx-waf","environment":[{"key":"SOMEENVVAR1","value":"SOMEENVVAR_VALUE1"},{"key":"SOMEENVVAR2","value":"SOMEENVVAR_VALUE2"}],"volumes":[]}],"feature":[],"service":[],"servers_count":3,"project_git_url":"info@optimum-web.com","project_name":"Sample2024","custom_stack_code":"sample2024"}}
\ No newline at end of file
+{"commonDomain":"","domainList":{},"region":"fsn1","zone":null,"server":"cx11","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":false,"disk_type":"pd-standart","cloud_token":"****************************","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"networks":["default_network"],"web":[{"name":"MyApp","code":"myapp","domain":"myapp.com","shared_ports":[{"host_port":"80","container_port":"8080"}],"versions":[],"custom":true,"type":"web","network":["default_network"],"restart":"always","timestamp":"2024-01-15T09:47:46.580Z","_id":"lreqrvv81ifrmng39","dockerhub_user":"trydirect","dockerhub_name":"nginx-wafs", "dockerhub_password":  "blabla", "environment":[{"key":"SOMEENVVAR1","value":"SOMEENVVAR_VALUE1"},{"key":"SOMEENVVAR2","value":"SOMEENVVAR_VALUE2"}],"volumes":[]}],"feature":[],"service":[],"servers_count":3,"project_git_url":"info@optimum-web.com","project_name":"Sample2024","custom_stack_code":"sample2024"}}
\ No newline at end of file

From 0431e3f349fd1297148d6c48825d05baab89982e Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Wed, 17 Jan 2024 17:23:17 +0200
Subject: [PATCH 138/284] issue-auth try_into for reference

---
 src/forms/stack/volume.rs    | 2 +-
 src/helpers/stack/builder.rs | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/forms/stack/volume.rs b/src/forms/stack/volume.rs
index 67263f08..b0079503 100644
--- a/src/forms/stack/volume.rs
+++ b/src/forms/stack/volume.rs
@@ -20,7 +20,7 @@ impl Volume {
     }
 }
 
-impl TryInto<dctypes::AdvancedVolumes> for Volume { //todo reference
+impl TryInto<dctypes::AdvancedVolumes> for &Volume {
     type Error = String;
     fn try_into(self) -> Result<dctypes::AdvancedVolumes, Self::Error> {
         let source = self.host_path.clone();
diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index dd36f239..8c67ec35 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -64,7 +64,7 @@ impl TryFrom<&forms::stack::App> for Service {
             Some(volumes) => {
                 let mut collector = vec![];
                 for volume in volumes {
-                    collector.push(Volumes::Advanced(volume.clone().try_into()?));
+                    collector.push(Volumes::Advanced(volume.try_into()?));
                 }
 
                 collector

From 5c2eb32d1c2270737ad4b7386f0acea067111755 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Wed, 17 Jan 2024 17:53:33 +0200
Subject: [PATCH 139/284] issue-auth forms/stack split

---
 src/forms/stack/compose_networks.rs |   6 +
 src/forms/stack/docker_image.rs     |  34 +++++
 src/forms/stack/domain_list.rs      |   5 +
 src/forms/stack/environment.rs      |   7 +
 src/forms/stack/feature.rs          |  12 ++
 src/forms/stack/icon.rs             |   8 ++
 src/forms/stack/icon_dark.rs        |   4 +
 src/forms/stack/icon_light.rs       |   8 ++
 src/forms/stack/mod.rs              | 204 +++++-----------------------
 src/forms/stack/network.rs          |   7 +
 src/forms/stack/price.rs            |   6 +
 src/forms/stack/requirements.rs     |  20 +++
 src/forms/stack/role.rs             |   7 +
 src/forms/stack/service.rs          |  13 ++
 src/forms/stack/service_networks.rs |   6 +
 src/forms/stack/var.rs              |   6 +
 src/forms/stack/version.rs          |  24 ++++
 src/forms/stack/web.rs              |  10 ++
 18 files changed, 217 insertions(+), 170 deletions(-)
 create mode 100644 src/forms/stack/compose_networks.rs
 create mode 100644 src/forms/stack/docker_image.rs
 create mode 100644 src/forms/stack/domain_list.rs
 create mode 100644 src/forms/stack/environment.rs
 create mode 100644 src/forms/stack/feature.rs
 create mode 100644 src/forms/stack/icon.rs
 create mode 100644 src/forms/stack/icon_dark.rs
 create mode 100644 src/forms/stack/icon_light.rs
 create mode 100644 src/forms/stack/network.rs
 create mode 100644 src/forms/stack/price.rs
 create mode 100644 src/forms/stack/requirements.rs
 create mode 100644 src/forms/stack/role.rs
 create mode 100644 src/forms/stack/service.rs
 create mode 100644 src/forms/stack/service_networks.rs
 create mode 100644 src/forms/stack/var.rs
 create mode 100644 src/forms/stack/version.rs
 create mode 100644 src/forms/stack/web.rs

diff --git a/src/forms/stack/compose_networks.rs b/src/forms/stack/compose_networks.rs
new file mode 100644
index 00000000..682d528b
--- /dev/null
+++ b/src/forms/stack/compose_networks.rs
@@ -0,0 +1,6 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct ComposeNetworks {
+    pub networks: Option<Vec<String>>,
+}
diff --git a/src/forms/stack/docker_image.rs b/src/forms/stack/docker_image.rs
new file mode 100644
index 00000000..b36a0994
--- /dev/null
+++ b/src/forms/stack/docker_image.rs
@@ -0,0 +1,34 @@
+use serde::{Deserialize, Serialize};
+use serde_valid::Validate;
+use std::fmt;
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct DockerImage {
+    #[validate(min_length = 3)]
+    #[validate(max_length = 50)]
+    pub dockerhub_user: Option<String>,
+    #[validate(min_length = 3)]
+    #[validate(max_length = 50)]
+    pub dockerhub_name: Option<String>,
+    #[validate(min_length = 3)]
+    #[validate(max_length = 100)]
+    pub dockerhub_image: Option<String>,
+}
+
+impl fmt::Display for DockerImage {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        let tag = "latest";
+
+        let dim = self.dockerhub_image.clone().unwrap_or("".to_string());
+        write!(
+            f,
+            "{}/{}:{}",
+            self.dockerhub_user
+                .clone()
+                .unwrap_or("trydirect".to_string())
+                .clone(),
+            self.dockerhub_name.clone().unwrap_or(dim),
+            tag
+        )
+    }
+}
diff --git a/src/forms/stack/domain_list.rs b/src/forms/stack/domain_list.rs
new file mode 100644
index 00000000..cf359eca
--- /dev/null
+++ b/src/forms/stack/domain_list.rs
@@ -0,0 +1,5 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct DomainList {}
diff --git a/src/forms/stack/environment.rs b/src/forms/stack/environment.rs
new file mode 100644
index 00000000..9b6e88d6
--- /dev/null
+++ b/src/forms/stack/environment.rs
@@ -0,0 +1,7 @@
+use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct Environment {
+    pub(crate) environment: Option<Vec<HashMap<String, String>>>,
+}
diff --git a/src/forms/stack/feature.rs b/src/forms/stack/feature.rs
new file mode 100644
index 00000000..8e5908c9
--- /dev/null
+++ b/src/forms/stack/feature.rs
@@ -0,0 +1,12 @@
+use serde::{Deserialize, Serialize};
+use crate::forms::stack::*;
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct Feature {
+    // #[serde(rename(deserialize = "sharedPorts"))]
+    // #[serde(rename(serialize = "shared_ports"))]
+    // #[serde(alias = "shared_ports")]
+    // pub shared_ports: Option<Vec<Port>>,
+    #[serde(flatten)]
+    pub app: App,
+}
diff --git a/src/forms/stack/icon.rs b/src/forms/stack/icon.rs
new file mode 100644
index 00000000..0bcae010
--- /dev/null
+++ b/src/forms/stack/icon.rs
@@ -0,0 +1,8 @@
+use serde::{Deserialize, Serialize};
+use crate::forms::stack::*;
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct Icon {
+    pub light: IconLight,
+    pub dark: IconDark,
+}
diff --git a/src/forms/stack/icon_dark.rs b/src/forms/stack/icon_dark.rs
new file mode 100644
index 00000000..73c543e4
--- /dev/null
+++ b/src/forms/stack/icon_dark.rs
@@ -0,0 +1,4 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct IconDark {}
diff --git a/src/forms/stack/icon_light.rs b/src/forms/stack/icon_light.rs
new file mode 100644
index 00000000..8e9e4b81
--- /dev/null
+++ b/src/forms/stack/icon_light.rs
@@ -0,0 +1,8 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct IconLight {
+    pub width: i64,
+    pub height: i64,
+    pub image: String,
+}
diff --git a/src/forms/stack/mod.rs b/src/forms/stack/mod.rs
index 2ea7aa04..94d1950c 100644
--- a/src/forms/stack/mod.rs
+++ b/src/forms/stack/mod.rs
@@ -1,10 +1,3 @@
-use serde::{Deserialize, Serialize};
-use serde_json::Value;
-use serde_valid::Validate;
-use std::collections::HashMap;
-use std::fmt;
-use crate::forms;
-
 mod app;
 mod custom;
 mod form;
@@ -12,6 +5,23 @@ mod port;
 mod payload;
 mod volumes;
 mod volume;
+mod role;
+mod requirements;
+mod docker_image;
+mod domain_list;
+mod var;
+mod price;
+mod network;
+mod environment;
+mod service_networks;
+mod compose_networks;
+mod web;
+mod feature;
+mod service;
+mod icon;
+mod icon_light;
+mod icon_dark;
+mod version;
 
 pub use app::*;
 pub use custom::*;
@@ -20,166 +30,20 @@ pub use port::*;
 pub use payload::*;
 pub use volumes::*;
 pub use volume::*;
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct Role {
-    pub role: Option<Vec<String>>,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct Requirements {
-    #[validate(min_length = 1)]
-    #[validate(max_length = 10)]
-    #[validate(pattern = r"^\d+\.?[0-9]+$")]
-    pub cpu: Option<String>,
-    #[validate(min_length = 1)]
-    #[validate(max_length = 10)]
-    #[validate(pattern = r"^\d+G$")]
-    #[serde(rename = "disk_size")]
-    pub disk_size: Option<String>,
-    #[serde(rename = "ram_size")]
-    #[validate(min_length = 1)]
-    #[validate(max_length = 10)]
-    #[validate(pattern = r"^\d+G$")]
-    pub ram_size: Option<String>,
-}
-
-
-// #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-// pub struct Ports {
-//     #[serde(rename(deserialize = "sharedPorts"))]
-//     #[serde(rename(serialize = "shared_ports"))]
-//     // #[serde(alias = "shared_ports")]
-//     pub shared_ports: Option<Vec<Port>>,
-//     pub ports: Option<Vec<String>>,
-// }
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct DockerImage {
-    #[validate(min_length = 3)]
-    #[validate(max_length = 50)]
-    pub dockerhub_user: Option<String>,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 50)]
-    pub dockerhub_name: Option<String>,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 100)]
-    pub dockerhub_image: Option<String>,
-}
-
-impl fmt::Display for DockerImage {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let tag = "latest";
-
-        let dim = self.dockerhub_image.clone().unwrap_or("".to_string());
-        write!(
-            f,
-            "{}/{}:{}",
-            self.dockerhub_user
-                .clone()
-                .unwrap_or("trydirect".to_string())
-                .clone(),
-            self.dockerhub_name.clone().unwrap_or(dim),
-            tag
-        )
-    }
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct DomainList {}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct Var {}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct Price {
-    pub value: f64,
-}
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct Network {
-    name: String,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct Environment {
-    pub(crate) environment: Option<Vec<HashMap<String, String>>>,
-}
-
-// pub(crate) type Networks = Option<Vec<String>>;
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct ServiceNetworks {
-    pub network: Option<Vec<String>>,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct ComposeNetworks {
-    pub networks: Option<Vec<String>>,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct Web {
-    #[serde(flatten)]
-    pub app: forms::stack::App,
-    pub custom: Option<bool>,
-    pub main: bool,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct Feature {
-    // #[serde(rename(deserialize = "sharedPorts"))]
-    // #[serde(rename(serialize = "shared_ports"))]
-    // #[serde(alias = "shared_ports")]
-    // pub shared_ports: Option<Vec<Port>>,
-    #[serde(flatten)]
-    pub app: forms::stack::App,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct Service {
-    // #[serde(rename(deserialize = "sharedPorts"))]
-    // #[serde(rename(serialize = "shared_ports"))]
-    // #[serde(alias = "shared_ports")]
-    // pub shared_ports: Option<Vec<Port>>,
-    #[serde(flatten)]
-    pub(crate) app: forms::stack::App,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct Icon {
-    pub light: IconLight,
-    pub dark: IconDark,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct IconLight {
-    pub width: i64,
-    pub height: i64,
-    pub image: String,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct IconDark {}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct Version {
-    #[serde(rename = "_etag")]
-    pub etag: Option<String>,
-    #[serde(rename = "_id")]
-    pub id: u32,
-    #[serde(rename = "_created")]
-    pub created: Option<String>,
-    #[serde(rename = "_updated")]
-    pub updated: Option<String>,
-    pub app_id: Option<u32>,
-    pub name: String,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 20)]
-    pub version: String,
-    #[serde(rename = "update_status")]
-    pub update_status: Option<String>,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 20)]
-    pub tag: String,
-}
+pub use role::*;
+pub use requirements::*;
+pub use docker_image::*;
+pub use domain_list::*;
+pub use var::*;
+pub use price::*;
+pub use network::*;
+pub use environment::*;
+pub use service_networks::*;
+pub use compose_networks::*;
+pub use web::*;
+pub use feature::*;
+pub use service::*;
+pub use icon::*;
+pub use icon_light::*;
+pub use icon_dark::*;
+pub use version::*;
diff --git a/src/forms/stack/network.rs b/src/forms/stack/network.rs
new file mode 100644
index 00000000..44a49707
--- /dev/null
+++ b/src/forms/stack/network.rs
@@ -0,0 +1,7 @@
+use serde::{Deserialize, Serialize};
+use serde_valid::Validate;
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct Network {
+    name: String,
+}
diff --git a/src/forms/stack/price.rs b/src/forms/stack/price.rs
new file mode 100644
index 00000000..06bbaeed
--- /dev/null
+++ b/src/forms/stack/price.rs
@@ -0,0 +1,6 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct Price {
+    pub value: f64,
+}
diff --git a/src/forms/stack/requirements.rs b/src/forms/stack/requirements.rs
new file mode 100644
index 00000000..02e6891b
--- /dev/null
+++ b/src/forms/stack/requirements.rs
@@ -0,0 +1,20 @@
+use serde::{Deserialize, Serialize};
+use serde_valid::Validate;
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct Requirements {
+    #[validate(min_length = 1)]
+    #[validate(max_length = 10)]
+    #[validate(pattern = r"^\d+\.?[0-9]+$")]
+    pub cpu: Option<String>,
+    #[validate(min_length = 1)]
+    #[validate(max_length = 10)]
+    #[validate(pattern = r"^\d+G$")]
+    #[serde(rename = "disk_size")]
+    pub disk_size: Option<String>,
+    #[serde(rename = "ram_size")]
+    #[validate(min_length = 1)]
+    #[validate(max_length = 10)]
+    #[validate(pattern = r"^\d+G$")]
+    pub ram_size: Option<String>,
+}
diff --git a/src/forms/stack/role.rs b/src/forms/stack/role.rs
new file mode 100644
index 00000000..5f5406aa
--- /dev/null
+++ b/src/forms/stack/role.rs
@@ -0,0 +1,7 @@
+use serde::{Deserialize, Serialize};
+use serde_valid::Validate;
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct Role {
+    pub role: Option<Vec<String>>,
+}
diff --git a/src/forms/stack/service.rs b/src/forms/stack/service.rs
new file mode 100644
index 00000000..1470233e
--- /dev/null
+++ b/src/forms/stack/service.rs
@@ -0,0 +1,13 @@
+use serde::{Deserialize, Serialize};
+use serde_valid::Validate;
+use crate::forms::stack::*;
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct Service {
+    // #[serde(rename(deserialize = "sharedPorts"))]
+    // #[serde(rename(serialize = "shared_ports"))]
+    // #[serde(alias = "shared_ports")]
+    // pub shared_ports: Option<Vec<Port>>,
+    #[serde(flatten)]
+    pub(crate) app: App,
+}
diff --git a/src/forms/stack/service_networks.rs b/src/forms/stack/service_networks.rs
new file mode 100644
index 00000000..de586f82
--- /dev/null
+++ b/src/forms/stack/service_networks.rs
@@ -0,0 +1,6 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct ServiceNetworks {
+    pub network: Option<Vec<String>>,
+}
diff --git a/src/forms/stack/var.rs b/src/forms/stack/var.rs
new file mode 100644
index 00000000..20721472
--- /dev/null
+++ b/src/forms/stack/var.rs
@@ -0,0 +1,6 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Var {}
+
diff --git a/src/forms/stack/version.rs b/src/forms/stack/version.rs
new file mode 100644
index 00000000..9e7dfb33
--- /dev/null
+++ b/src/forms/stack/version.rs
@@ -0,0 +1,24 @@
+use serde::{Deserialize, Serialize};
+use serde_valid::Validate;
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct Version {
+    #[serde(rename = "_etag")]
+    pub etag: Option<String>,
+    #[serde(rename = "_id")]
+    pub id: u32,
+    #[serde(rename = "_created")]
+    pub created: Option<String>,
+    #[serde(rename = "_updated")]
+    pub updated: Option<String>,
+    pub app_id: Option<u32>,
+    pub name: String,
+    #[validate(min_length = 3)]
+    #[validate(max_length = 20)]
+    pub version: String,
+    #[serde(rename = "update_status")]
+    pub update_status: Option<String>,
+    #[validate(min_length = 3)]
+    #[validate(max_length = 20)]
+    pub tag: String,
+}
diff --git a/src/forms/stack/web.rs b/src/forms/stack/web.rs
new file mode 100644
index 00000000..776ea0a1
--- /dev/null
+++ b/src/forms/stack/web.rs
@@ -0,0 +1,10 @@
+use serde::{Deserialize, Serialize};
+use crate::forms::stack::*;
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct Web {
+    #[serde(flatten)]
+    pub app: App,
+    pub custom: Option<bool>,
+    pub main: bool,
+}

From 2952f3b1203ec221c1e11b2042cf2143e867d28f Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Wed, 17 Jan 2024 18:17:54 +0200
Subject: [PATCH 140/284] issue-auth builder_config

---
 src/forms/stack/app.rs              |  53 ++++++++++++
 src/forms/stack/compose_networks.rs |  35 ++++++++
 src/forms/stack/service_networks.rs |  16 ++++
 src/helpers/stack/builder.rs        | 127 ++--------------------------
 src/helpers/stack/builder_config.rs |   9 ++
 src/helpers/stack/mod.rs            |   3 +
 6 files changed, 122 insertions(+), 121 deletions(-)
 create mode 100644 src/helpers/stack/builder_config.rs

diff --git a/src/forms/stack/app.rs b/src/forms/stack/app.rs
index f38a81f9..6e1f356b 100644
--- a/src/forms/stack/app.rs
+++ b/src/forms/stack/app.rs
@@ -104,3 +104,56 @@ impl AsRef<forms::stack::DockerImage> for App {
         &self.docker_image
     }
 }
+
+impl TryFrom<&App> for dctypes::Service {
+    type Error = String;
+
+    fn try_from(app: &App) -> Result<Self, Self::Error> {
+        let mut service = dctypes::Service {
+            image: Some(app.docker_image.to_string()),
+            ..Default::default()
+        };
+
+        let networks = dctypes::Networks::try_from(&app.network).unwrap_or_default();
+        let ports: Vec<dctypes::Port> = match &app.ports {
+            Some(ports) => {
+                let mut collector = vec![];
+                for port in ports {
+                    collector.push(port.try_into()?);
+                }
+                collector
+            }
+            None => vec![]
+        };
+
+        let volumes: Vec<dctypes::Volumes> = match &app.volumes {
+            Some(volumes) => {
+                let mut collector = vec![];
+                for volume in volumes {
+                    collector.push(dctypes::Volumes::Advanced(volume.try_into()?));
+                }
+
+                collector
+            },
+            None => vec![]
+        };
+
+        let mut envs = IndexMap::new();
+        for item in app.environment.environment.clone().unwrap_or_default() {
+            let items = item
+                .into_iter()
+                .map(|(k, v)| (k, Some(dctypes::SingleValue::String(v.clone()))))
+                .collect::<IndexMap<_, _>>();
+
+            envs.extend(items);
+        }
+
+        service.networks = networks;
+        service.ports = dctypes::Ports::Long(ports);
+        service.restart = Some("always".to_owned());
+        service.volumes = volumes;
+        service.environment = dctypes::Environment::KvPair(envs);
+
+        Ok(service)
+    }
+}
diff --git a/src/forms/stack/compose_networks.rs b/src/forms/stack/compose_networks.rs
index 682d528b..0dec3bf3 100644
--- a/src/forms/stack/compose_networks.rs
+++ b/src/forms/stack/compose_networks.rs
@@ -1,6 +1,41 @@
 use serde::{Deserialize, Serialize};
+use docker_compose_types as dctypes;
+use indexmap::IndexMap;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct ComposeNetworks {
     pub networks: Option<Vec<String>>,
 }
+
+impl Into<IndexMap<String, dctypes::MapOrEmpty<dctypes::NetworkSettings>>> for ComposeNetworks {
+    fn into(self) -> IndexMap<String, dctypes::MapOrEmpty<dctypes::NetworkSettings>> {
+        let mut networks = vec!["default_network".to_string()];
+        if self.networks.is_some() {
+            networks.append(&mut self.networks.unwrap());
+        }
+        let networks = networks
+            .into_iter()
+            .map(|net| {
+                (
+                    net,
+                    dctypes::MapOrEmpty::Map(dctypes::NetworkSettings {
+                        attachable: false,
+                        driver: None,
+                        driver_opts: Default::default(),
+                        enable_ipv6: false,
+                        internal: false,
+                        // external: None,
+                        external: Some(dctypes::ComposeNetwork::Bool(true)),
+                        ipam: None,
+                        labels: Default::default(),
+                        name: Some("default".to_string()),
+                    }),
+                )
+            })
+            .collect::<IndexMap<String, _>>();
+
+        tracing::debug!("networks collected {:?}", &networks);
+
+        networks
+    }
+}
diff --git a/src/forms/stack/service_networks.rs b/src/forms/stack/service_networks.rs
index de586f82..55c729e2 100644
--- a/src/forms/stack/service_networks.rs
+++ b/src/forms/stack/service_networks.rs
@@ -1,6 +1,22 @@
 use serde::{Deserialize, Serialize};
+use docker_compose_types as dctypes;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct ServiceNetworks {
     pub network: Option<Vec<String>>,
 }
+
+impl TryFrom<&ServiceNetworks> for dctypes::Networks {
+    type Error = ();
+
+    fn try_from(service_networks: &ServiceNetworks) -> Result<dctypes::Networks, Self::Error> {
+        let mut result = vec!["default_network".to_string()];
+        service_networks.network.as_ref().map(|networks| {
+            for n in networks {
+                result.push(n.to_string());
+            }
+        });
+
+        Ok(dctypes::Networks::Simple(result))
+    }
+}
diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index 8c67ec35..9e7daa2b 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -1,20 +1,8 @@
 use crate::forms;
-use docker_compose_types::{
-    AdvancedVolumes, Compose, ComposeNetwork, ComposeNetworkSettingDetails, ComposeNetworks,
-    ComposeVolume, Entrypoint, Environment, MapOrEmpty, NetworkSettings, Networks, Port, Ports,
-    PublishedPort, Service, Services, SingleValue, TopLevelVolumes, Volumes,
-};
+use docker_compose_types as dctypes;
 use crate::models;
-use indexmap::IndexMap;
 use serde_yaml;
-#[derive(Clone, Debug)]
-struct Config {}
-
-impl Default for Config {
-    fn default() -> Self {
-        Config {}
-    }
-}
+use crate::helpers::stack::*;
 
 /// A builder for constructing docker compose.
 #[derive(Clone, Debug)]
@@ -23,109 +11,6 @@ pub struct DcBuilder {
     pub(crate) stack: models::Stack,
 }
 
-impl TryFrom<&forms::stack::ServiceNetworks> for Networks {
-    type Error = ();
-
-    fn try_from(service_networks: &forms::stack::ServiceNetworks) -> Result<Networks, Self::Error> {
-        let mut result = vec!["default_network".to_string()];
-        service_networks.network.as_ref().map(|networks| {
-            for n in networks {
-                result.push(n.to_string());
-            }
-        });
-
-        Ok(Networks::Simple(result))
-    }
-}
-
-
-impl TryFrom<&forms::stack::App> for Service {
-    type Error = String;
-
-    fn try_from(app: &forms::stack::App) -> Result<Self, Self::Error> {
-        let mut service = Service {
-            image: Some(app.docker_image.to_string()),
-            ..Default::default()
-        };
-
-        let networks = Networks::try_from(&app.network).unwrap_or_default();
-        let ports: Vec<Port> = match &app.ports {
-            Some(ports) => {
-                let mut collector = vec![];
-                for port in ports {
-                    collector.push(port.try_into()?);
-                }
-                collector
-            }
-            None => vec![]
-        };
-
-        let volumes: Vec<Volumes> = match &app.volumes {
-            Some(volumes) => {
-                let mut collector = vec![];
-                for volume in volumes {
-                    collector.push(Volumes::Advanced(volume.try_into()?));
-                }
-
-                collector
-            },
-            None => vec![]
-        };
-
-        let mut envs = IndexMap::new();
-        for item in app.environment.environment.clone().unwrap_or_default() {
-            let items = item
-                .into_iter()
-                .map(|(k, v)| (k, Some(SingleValue::String(v.clone()))))
-                .collect::<IndexMap<_, _>>();
-
-            envs.extend(items);
-        }
-
-        service.networks = networks;
-        service.ports = Ports::Long(ports);
-        service.restart = Some("always".to_owned());
-        service.volumes = volumes;
-        service.environment = Environment::KvPair(envs);
-
-        Ok(service)
-    }
-}
-
-impl Into<IndexMap<String, MapOrEmpty<NetworkSettings>>> for forms::stack::ComposeNetworks {
-    fn into(self) -> IndexMap<String, MapOrEmpty<NetworkSettings>> {
-        // tracing::debug!("networks found {:?}", self.networks);
-        let mut networks = vec!["default_network".to_string()];
-        if self.networks.is_some() {
-            networks.append(&mut self.networks.unwrap());
-        }
-        let networks = networks
-            .into_iter()
-            .map(|net| {
-                (
-                    net,
-                    MapOrEmpty::Map(NetworkSettings {
-                        attachable: false,
-                        driver: None,
-                        driver_opts: Default::default(),
-                        enable_ipv6: false,
-                        internal: false,
-                        // external: None,
-                        external: Some(ComposeNetwork::Bool(true)),
-                        ipam: None,
-                        labels: Default::default(),
-                        name: Some("default".to_string()),
-                    }),
-                )
-            })
-            .collect::<IndexMap<String, _>>();
-
-        tracing::debug!("networks collected {:?}", &networks);
-
-        networks
-    }
-}
-
 impl DcBuilder {
     pub fn new(stack: models::Stack) -> Self {
         DcBuilder {
@@ -136,7 +21,7 @@ impl DcBuilder {
 
     #[tracing::instrument(name = "building stack")]
     pub fn build(&self) -> Result<String, String> {
-        let mut compose_content = Compose {
+        let mut compose_content = dctypes::Compose {
             version: Some("3.8".to_string()),
             ..Default::default()
         };
@@ -146,14 +31,14 @@ impl DcBuilder {
         let  named_volumes = apps.custom.named_volumes()?;
 
         let networks = apps.custom.networks.clone();
-        compose_content.networks = ComposeNetworks(networks.into());
+        compose_content.networks = dctypes::ComposeNetworks(networks.into());
 
         if !named_volumes.is_empty() {
-            compose_content.volumes = TopLevelVolumes(named_volumes);
+            compose_content.volumes = dctypes::TopLevelVolumes(named_volumes);
         }
 
         tracing::debug!("services {:?}", &services);
-        compose_content.services = Services(services);
+        compose_content.services = dctypes::Services(services);
 
         let fname = format!("./files/{}.yml", self.stack.stack_id);
         tracing::debug!("Saving docker compose to file {:?}", fname);
diff --git a/src/helpers/stack/builder_config.rs b/src/helpers/stack/builder_config.rs
new file mode 100644
index 00000000..7f50a24f
--- /dev/null
+++ b/src/helpers/stack/builder_config.rs
@@ -0,0 +1,9 @@
+#[derive(Clone, Debug)]
+pub struct Config {}
+
+impl Default for Config {
+    fn default() -> Self {
+        Config {}
+    }
+}
+
diff --git a/src/helpers/stack/mod.rs b/src/helpers/stack/mod.rs
index 9b5530b5..72ce5373 100644
--- a/src/helpers/stack/mod.rs
+++ b/src/helpers/stack/mod.rs
@@ -1 +1,4 @@
 pub(crate) mod builder;
+mod builder_config;
+
+pub use builder_config::*;

From d557aa3d683871e88fda806b0796d3b1e6cc3729 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Wed, 17 Jan 2024 22:00:19 +0200
Subject: [PATCH 141/284] broken, dockerhub validator

---
 Cargo.lock                      |  12 +-
 Cargo.toml                      |  18 +--
 src/forms/stack.rs              | 198 ++++++++++++++++++++++++--------
 src/helpers/dockerhub.rs        |  44 ++++++-
 src/helpers/stack/builder.rs    |   2 +-
 src/routes/stack/add.rs         |   3 +
 src/routes/stack/update.rs      |  14 +--
 tests/app.json                  |   2 +-
 tests/custom-stack-payload.json |   2 +-
 tests/web-item.json             |   2 +-
 10 files changed, 222 insertions(+), 75 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 6bc8dd89..ff8780bc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2823,9 +2823,9 @@ dependencies = [
 
 [[package]]
 name = "serde_valid"
-version = "0.17.0"
+version = "0.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ede56947572525b2c9640542edfa35bbc2f575fa03f97d4d68e9eb4e4c5136b"
+checksum = "70c0e00fab6460447391a1981c21341746bc2d0178a7c46a3bbf667f450ac6e4"
 dependencies = [
  "indexmap 2.1.0",
  "itertools",
@@ -2843,9 +2843,9 @@ dependencies = [
 
 [[package]]
 name = "serde_valid_derive"
-version = "0.17.0"
+version = "0.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79ffdd7f3a5dea22dc86747bc02f5292a091977383c0124e7d77fc4fd120c486"
+checksum = "88c60a851514741a6088b2cd18eefb3f0d02ff3a1c87234de47153f2724d395d"
 dependencies = [
  "paste",
  "proc-macro-error",
@@ -2857,9 +2857,9 @@ dependencies = [
 
 [[package]]
 name = "serde_valid_literal"
-version = "0.17.0"
+version = "0.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be811ca7379637055f6474a1c0d0ed985c86966e580279ec980f77816b8f41b0"
+checksum = "aced4f1b31605a2b55eeacf2ec4dcbd96583263e9ded17eed1d41ab75915d12e"
 dependencies = [
  "paste",
  "regex",
diff --git a/Cargo.toml b/Cargo.toml
index 75df059a..0fdf1122 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,20 +19,20 @@ name = "console"
 
 [dependencies]
 actix-web = "4.3.1"
-chrono = { version = "0.4.26", features = ["time", "serde"] }
-config = "0.13.3"
-reqwest = { version = "0.11.17", features = ["json", "blocking"] }
-serde = { version = "1.0.162", features = ["derive"] }
+chrono = { version = "0.4.29", features = ["time", "serde"] }
+config = "0.13.4"
+reqwest = { version = "0.11.23", features = ["json", "blocking"] }
+serde = { version = "1.0.195", features = ["derive"] }
 tokio = { version = "1.28.1", features = ["full"] }
 tracing = { version = "0.1.37", features = ["log"] }
 tracing-bunyan-formatter = "0.3.8"
-tracing-log = "0.1.3"
-tracing-subscriber = { version = "0.3.17", features = ["registry", "env-filter"] }
+tracing-log = "0.1.4"
+tracing-subscriber = { version = "0.3.18", features = ["registry", "env-filter"] }
 uuid = { version = "1.3.4", features = ["v4", "serde"] }
 thiserror = "1.0"
-serde_valid = {  version = "0.17.0", features = [] }
-serde_json = { version = "1.0.105", features = [] }
-serde_derive = "1.0.188"
+serde_valid = "0.18.0"
+serde_json = { version = "1.0.111", features = [] }
+serde_derive = "1.0.195"
 actix-web-httpauth = "0.8.1"
 actix-cors = "0.6.4"
 tracing-actix-web = "0.7.7"
diff --git a/src/forms/stack.rs b/src/forms/stack.rs
index 16c31647..b799125b 100644
--- a/src/forms/stack.rs
+++ b/src/forms/stack.rs
@@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
 use serde_json::Value;
 use serde_valid::Validate;
 use std::fmt;
-use crate::helpers::{login, docker_image_exists};
+use crate::helpers::{login, docker_image_exists, DockerHubCreds, DockerHubToken};
 use tokio::runtime::Runtime;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
@@ -25,54 +25,156 @@ pub struct Requirements {
     #[validate(min_length = 1)]
     #[validate(max_length = 10)]
     #[validate(pattern = r"^\d+G$")]
-    pub ram_size: Option<String>,
+    pub ram_size: Option<String>
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Port {
-    #[validate(pattern = r"^\d+$")]
+    #[validate(pattern = r"^\d{2,6}+$")]
     pub host_port: Option<String>,
-    #[validate(pattern = r"^\d+$")]
+    #[validate(pattern = r"^\d{2,6}+$")]
     pub container_port: String,
     #[validate(enumerate("tcp", "udp"))]
     pub protocol: Option<String>
 }
 
-fn validate_dockerhub_image(dockerhub: &DockerImage) -> Result<(), serde_valid::validation::Error> {
+// fn validate_dockerhub_image(docker_image: &DockerImage) -> Result<(), serde_valid::validation::Error> {
+//
+//
+//     let rt = Runtime::new().unwrap();
+//
+//     // Spawn a blocking function onto the runtime
+//     rt.block_on(async {
+//         let client = reqwest::Client::new();
+//         let dockerhub_api_url = format!(
+//             "https://hub.docker.com/v2/repositories/{}/{}",
+//             docker_image.dockerhub_user.as_ref().unwrap(),
+//             docker_image.dockerhub_name.as_ref().unwrap()
+//         );
+//
+//         let response = client.get(&dockerhub_api_url)
+//             .send()
+//             .await;
+//
+//         match response {
+//             Ok(resp) => {
+//                 if resp.status().is_success() {
+//                     Ok(())
+//                 } else {
+//                     Err(serde_valid::validation::Error::Custom("Not exists".to_string()))
+//                 }
+//             },
+//             Err(_) => Err(serde_valid::validation::Error::Custom("Not exists".to_string()))
+//         }
+//     })
+// }
 
-    println!("validate dockerhub image {:?}", dockerhub);
+
+fn validate_dockerhub_image(docker_image: &DockerImage) -> Result<(), serde_valid::validation::Error> {
+    println!("validate dockerhub image {:?}", docker_image);
     tracing::debug!("Validate image at hub.docker.com...");
-    // Create the runtime
-    let rt = Runtime::new().unwrap();
-
-    // Spawn a blocking function onto the runtime
-    rt.block_on(async {
-        let result = login(
-            dockerhub.dockerhub_user.clone().unwrap_or("".to_string()).as_ref(),
-            dockerhub.dockerhub_password.clone().unwrap_or("".to_string()).as_ref()
-        )
-            .await
-            .map_err(|err| serde_valid::validation::Error::Custom(format!("{:?}", err)))?;
-
-        match result.token {
-            None => {
-                return Err(serde_valid::validation::Error::Custom(
-                    "Could not access docker image repository, please check credentials.".to_owned(),
-                ));
-            },
-            Some(tok) => {
-
-                tracing::debug!("We were able to login hub.docker.com!");
-                docker_image_exists(
-                    dockerhub.dockerhub_user.clone().unwrap().as_str(),
-                    dockerhub.dockerhub_name.clone().unwrap().as_str(), tok)
-                    .await
-                    .map_err(|err| serde_valid::validation::Error::Custom("Not exists".to_string()))
-                    .map(|_| ())
-            }
-        }
-    })
+
+    let endpoint = "https://hub.docker.com/v2/users/login";
+    let creds = DockerHubCreds {
+        username: docker_image.dockerhub_user.as_ref().unwrap(),
+        password: docker_image.dockerhub_password.as_ref().unwrap()
+    };
+    let client = reqwest::blocking::Client::new();
+    client.post(endpoint)
+        .json(&creds)
+        .send()
+        .map_err(|err| serde_valid::validation::Error::Custom(format!("{:?}", err)))?
+        .json::<DockerHubToken>()
+        .map_err(|err| serde_valid::validation::Error::Custom(format!("{:?}", err)))
+        .and_then(|token| {
+            tracing::debug!("got token {:?}", token);
+            Ok(())
+        })
 }
+
+//
+// fn validate_dockerhub_image(docker_image: &DockerImage) -> Result<(), serde_valid::validation::Error> {
+//
+//     println!("validate dockerhub image {:?}", docker_image);
+//     tracing::debug!("Validate image at hub.docker.com...");
+//
+//     let endpoint = "https://hub.docker.com/v2/users/login";
+//     let creds = DockerHubCreds {
+//         username: docker_image.dockerhub_user.as_ref().unwrap(),
+//         password: docker_image.dockerhub_password.as_ref().unwrap()
+//     };
+//     reqwest::blocking::Client::new()
+//         .post(endpoint)
+//         .json(&creds)
+//         .send()
+//         .map_err(|err| serde_valid::validation::Error::Custom(format!("{:?}", err)))?
+//         .json::<DockerHubToken>()
+//         .map_err(|err| serde_valid::validation::Error::Custom(format!("{:?}", err)))
+//         .and_then(|token|{
+//             tracing::debug!("got token {:?}", token);
+//             Ok(())
+//         })
+//
+//
+//
+//     // Create the runtime
+//     // let rt = Runtime::new().unwrap();
+//     //
+//     // // Spawn a blocking function onto the runtime
+//     // rt.block_on(async {
+//     //     let result = login(
+//     //         docker_image.dockerhub_user.clone().unwrap_or("".to_string()).as_ref(),
+//     //         docker_image.dockerhub_password.clone().unwrap_or("".to_string()).as_ref()
+//     //     )
+//     //         .await
+//     //         .map_err(|err| serde_valid::validation::Error::Custom(format!("{:?}", err)))?;
+//     //
+//     //     match result.token {
+//     //         None => {
+//     //             return Err(serde_valid::validation::Error::Custom(
+//     //                 "Could not access docker image repository, please check credentials.".to_owned(),
+//     //             ));
+//     //         },
+//     //         Some(tok) => {
+//     //             tracing::debug!("We were able to login hub.docker.com!");
+//     //             docker_image_exists(
+//     //                 docker_image.dockerhub_user.clone().unwrap().as_str(),
+//     //                 docker_image.dockerhub_name.clone().unwrap().as_str(), tok)
+//     //                 .await
+//     //                 .map_err(|err| serde_valid::validation::Error::Custom("Not exists".to_string()))
+//     //                 .map(|_| ())
+//     //         }
+//     //     }
+//     // })
+//
+// }
+
+// fn validate_dockerhub_image(docker_image: &DockerImage) -> Result<(), serde_valid::validation::Error> {
+//
+//     tracing::debug!("validate dockerhub image {:?}", docker_image);
+//     let endpoint = "https://hub.docker.com/v2/users/login";
+//     let creds = DockerHubCreds {
+//         username: docker_image.dockerhub_user.as_ref().unwrap(),
+//         password: docker_image.dockerhub_password.as_ref().unwrap()
+//     };
+//     reqwest::blocking::Client::new()
+//         .post(endpoint)
+//         .json(&creds)
+//         .send()
+//         .map_err(|err| serde_valid::validation::Error::Custom(format!("{:?}", err)))?
+//         .json::<DockerHubToken>()
+//         .map_err(|err| serde_valid::validation::Error::Custom(format!("{:?}", err)))
+//         .and_then(|token|{
+//             docker_image_exists(
+//                 docker_image.dockerhub_user.clone().unwrap().as_str(),
+//                 docker_image.dockerhub_name.clone().unwrap().as_str(),
+//                 token
+//             )
+//                 .map_err(|err| serde_valid::validation::Error::Custom("Not exists".to_string()))
+//                 .map(|_| ())
+//         })
+// }
+
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 #[validate(custom(|dockerhub|validate_dockerhub_image(dockerhub)))]
 pub struct DockerImage {
@@ -86,8 +188,6 @@ pub struct DockerImage {
     pub dockerhub_name: Option<String>,
     #[validate(min_length = 3)]
     #[validate(max_length = 100)]
-    #[validate(pattern = r"^(?:(?=[^:\/]{1,253})(?!-)[a-zA-Z0-9-]{1,63}(?<!-)(?:\.(?!-)[a-zA-Z0-9-]{1,63}(?<!-))*(?::[0-9]{1,5})?/)?((?![._-])(?:[a-z0-9._-]*)(?<![._-])(?:/(?![._-])[a-z0-9._-]*(?<![._-]))*)(?::(?![.-])[a-zA-Z0-9_.-]{1,128})?$
-")]
     pub dockerhub_image: Option<String>,
     pub dockerhub_password: Option<String>,
 }
@@ -117,8 +217,7 @@ impl AsRef<DockerImage> for App {
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct StackForm {
-    // #[validate(min_length=2)]
-    // #[validate(max_length=255)]
+    #[validate(max_length=255)]
     #[serde(rename = "commonDomain")]
     pub common_domain: Option<String>,
     pub domain_list: Option<DomainList>,
@@ -158,6 +257,7 @@ pub struct StackForm {
     #[validate(min_length = 3)]
     #[validate(max_length = 50)]
     pub selected_plan: String,
+    #[validate]
     pub custom: Custom,
 }
 
@@ -209,8 +309,11 @@ pub struct Price {
 }
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Custom {
+    #[validate]
     pub web: Vec<Web>,
+    #[validate]
     pub feature: Option<Vec<Feature>>,
+    #[validate]
     pub service: Option<Vec<Service>>,
     #[validate(minimum = 0)]
     #[validate(maximum = 10)]
@@ -265,8 +368,10 @@ pub struct App {
     pub default: Option<bool>,
     pub versions: Option<Vec<Version>>,
     #[serde(flatten)]
+    #[validate]
     pub docker_image: DockerImage,
     #[serde(flatten)]
+    #[validate]
     pub requirements: Requirements,
     #[validate(minimum = 1)]
     pub popularity: Option<u32>,
@@ -296,10 +401,8 @@ pub struct App {
     pub environment: Environment,
     #[serde(flatten)]
     pub network: ServiceNetworks,
-    #[serde(rename(deserialize = "sharedPorts"))]
-    #[serde(rename(serialize = "shared_ports"))]
-    #[serde(alias = "shared_ports")]
-    pub ports: Option<Vec<Port>>,
+    #[validate]
+    pub shared_ports: Option<Vec<Port>>,
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
@@ -334,23 +437,26 @@ pub struct ComposeNetworks {
     pub networks: Option<Vec<String>>,
 }
 
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Web {
     #[serde(flatten)]
+    #[validate]
     pub app: App,
     pub custom: Option<bool>,
     pub main: Option<bool>,
 }
 
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Feature {
     #[serde(flatten)]
+    #[validate]
     pub app: App,
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Service {
     #[serde(flatten)]
+    #[validate]
     pub(crate) app: App,
 }
 
diff --git a/src/helpers/dockerhub.rs b/src/helpers/dockerhub.rs
index 108b4387..15bc6c90 100644
--- a/src/helpers/dockerhub.rs
+++ b/src/helpers/dockerhub.rs
@@ -11,8 +11,8 @@ pub struct DockerHubToken {
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct DockerHubCreds<'a>  {
-    username: &'a str,
-    password: &'a str
+    pub(crate) username: &'a str,
+    pub(crate) password: &'a str
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
@@ -102,3 +102,43 @@ pub async fn docker_image_exists(user: &str, repo: &str, token: String) -> Resul
         Err(String::from("There were no active tags found in this repository"))
     }
 }
+
+// pub fn docker_image_exists(user: &str, repo: &str, token: DockerHubToken) -> Result<bool, serde_valid::validation::Error> {
+//     // get repo images
+//     let tags_url = format!("https://hub.docker.com/v2/namespaces/{}/repositories/{}/tags",
+//                            user, repo);
+//     //
+//     // let tags = reqwest::Client::new()
+//     //     .get(tags_url)
+//     //     .header("Accept", "application/json")
+//     //     .bearer_auth(token)
+//     //     .send()
+//     //     .await
+//     //     .map_err(|err| format!("{}", err))?
+//     //     .json::<TagResult>()
+//     //     // .json::<serde_json::Value>()
+//     //     .await
+//     //     .map_err(|err| format!("{}", err))?;
+//
+//     reqwest::blocking::Client::new()
+//         .get(tags_url)
+//         .header("Accept", "application/json")
+//         .bearer_auth(token.token.unwrap())
+//         .send()
+//         .map_err(|err| serde_valid::validation::Error::Custom(format!("{:?}", err)))?
+//         .json::<TagResult>()
+//         .map_err(|err| serde_valid::validation::Error::Custom(format!("{:?}", err)))
+//         .and_then(|tags|{
+//             println!("tags count: {:?}", tags.count);
+//             if tags.count > 0 {
+//                 // let's find at least one active tag
+//                 let active = tags.results
+//                     .into_iter()
+//                     .any(|tag| tag.tag_status.contains("active") );
+//                 Ok(active)
+//             } else {
+//                 Err(serde_valid::validation::Error::Custom("There were no active tags found in this repository".to_string()))
+//             }
+//         })
+//
+// }
diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index 84854d10..024bd26c 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -121,7 +121,7 @@ impl TryIntoService for App {
             .try_into()
             .unwrap_or_default();
 
-        let ports: Vec<Port> = self.ports
+        let ports: Vec<Port> = self.shared_ports
             .clone()
             .unwrap_or_default()
             .into_iter()
diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs
index b5767346..a399f017 100644
--- a/src/routes/stack/add.rs
+++ b/src/routes/stack/add.rs
@@ -55,6 +55,7 @@ async fn stack_exists(pool: &PgPool, stack_name: &String) -> Result<(), Error> {
 }
 
 async fn body_into_form(body: Bytes) -> Result<StackForm, Error> {
+    tracing::debug!("Body into form ...");
     let body_bytes = actix_web::body::to_bytes(body).await.unwrap();
     let body_str = str::from_utf8(&body_bytes)
         .map_err(|_err| JsonResponse::<StackForm>::build().internal_server_error(_err.to_string()))?;
@@ -66,6 +67,8 @@ async fn body_into_form(body: Bytes) -> Result<StackForm, Error> {
         })
         .and_then(|form: StackForm| {
 
+
+            tracing::debug!("Validating form ...");
             if !form.validate().is_ok() {
                 let errors = form.validate().unwrap_err();
                 let err_msg = format!("Invalid data received {:?}", &errors.to_string());
diff --git a/src/routes/stack/update.rs b/src/routes/stack/update.rs
index ed4d3b71..e62dd94d 100644
--- a/src/routes/stack/update.rs
+++ b/src/routes/stack/update.rs
@@ -60,14 +60,6 @@ pub async fn update(
     );
     let _request_span_guard = request_span.enter(); // ->exit
 
-    tracing::info!("request_id {} Updating '{}' '{}'",
-        request_id,
-        form.custom.project_name,
-        form.region
-    );
-
-    let query_span = tracing::info_span!("Update stack details in db.");
-
     if !form.validate().is_ok() {
         let errors = form.validate().unwrap_err();
         let err_msg = format!("Invalid data received {:?}", &errors.to_string());
@@ -76,6 +68,12 @@ pub async fn update(
         return Err(JsonResponse::<models::Stack>::build().bad_request(errors.to_string()));
     }
 
+    tracing::info!("request_id {} Updating '{}' '{}'",
+        request_id,
+        form.custom.project_name,
+        form.region
+    );
+    let query_span = tracing::info_span!("Update stack details in db.");
     let body: Value = match serde_json::to_value::<StackForm>(form.into_inner()) {
         Ok(body) => body,
         Err(err) => {
diff --git a/tests/app.json b/tests/app.json
index cb4efb1a..0a377171 100644
--- a/tests/app.json
+++ b/tests/app.json
@@ -69,7 +69,7 @@
   "links": [
   ],
   "domain": "latestports.com",
-  "sharedPorts": [
+  "shared_ports": [
     {
       "host_port": "5000",
       "container_port": "5000"
diff --git a/tests/custom-stack-payload.json b/tests/custom-stack-payload.json
index d699f892..ba6de202 100644
--- a/tests/custom-stack-payload.json
+++ b/tests/custom-stack-payload.json
@@ -1 +1 @@
-{"commonDomain":"","domainList":{},"region":"fsn1","zone":null,"server":"cx11","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":false,"disk_type":"pd-standart","cloud_token":"****************************","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"networks":["default_network"],"web":[{"name":"MyApp","code":"myapp","domain":"myapp.com","shared_ports":[{"host_port":"80","container_port":"8080"}],"versions":[],"custom":true,"type":"web","network":["default_network"],"restart":"always","timestamp":"2024-01-15T09:47:46.580Z","_id":"lreqrvv81ifrmng39","dockerhub_user":"trydirect","dockerhub_name":"nginx-wafs", "dockerhub_password":  "blabla", "environment":[{"key":"SOMEENVVAR1","value":"SOMEENVVAR_VALUE1"},{"key":"SOMEENVVAR2","value":"SOMEENVVAR_VALUE2"}],"volumes":[]}],"feature":[],"service":[],"servers_count":3,"project_git_url":"info@optimum-web.com","project_name":"Sample2024","custom_stack_code":"sample2024"}}
\ No newline at end of file
+{"commonDomain":"","domainList":{},"region":"fsn1","zone":null,"server":"cx11","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":false,"disk_type":"pd-standart","cloud_token":"****************************","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"networks":["default_network"],"web":[{"name":"MyApp","code":"myapp","domain":"myapp.com","shared_ports":[{"host_port":"80","container_port":""}],"versions":[],"custom":true,"type":"web","network":["default_network"],"restart":"always","timestamp":"2024-01-15T09:47:46.580Z","_id":"lreqrvv81ifrmng39","dockerhub_user":"trydirect","dockerhub_name":"nginx-wafs", "dockerhub_password":  "blabla", "environment":[{"key":"SOMEENVVAR1","value":"SOMEENVVAR_VALUE1"},{"key":"SOMEENVVAR2","value":"SOMEENVVAR_VALUE2"}],"volumes":[]}],"feature":[],"service":[],"servers_count":3,"project_git_url":"info@optimum-web.com","project_name":"Sample2024","custom_stack_code":"sample2024"}}
\ No newline at end of file
diff --git a/tests/web-item.json b/tests/web-item.json
index cb4efb1a..0a377171 100644
--- a/tests/web-item.json
+++ b/tests/web-item.json
@@ -69,7 +69,7 @@
   "links": [
   ],
   "domain": "latestports.com",
-  "sharedPorts": [
+  "shared_ports": [
     {
       "host_port": "5000",
       "container_port": "5000"

From fd4f77b7ccb7b3eee75d2f4910917a7e617f8fb0 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Thu, 18 Jan 2024 10:49:10 +0200
Subject: [PATCH 142/284] default_network name fix

---
 src/helpers/stack/builder.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index 024bd26c..7652eb70 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -187,7 +187,7 @@ impl Into<IndexMap<String, MapOrEmpty<NetworkSettings>>> for stack::ComposeNetwo
                          external: Some(ComposeNetwork::Bool(true)),
                          ipam: None,
                          labels: Default::default(),
-                         name: Some("default".to_string()),
+                         name: Some("default_network".to_string()),
                      }
                  ))
             }

From b5d6de7966efc36c821bd10a859b465c7f0d29ef Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Thu, 18 Jan 2024 17:43:43 +0200
Subject: [PATCH 143/284] request to hub.docker.com

---
 Cargo.lock                      | 46 ++++++++++++++++++
 Cargo.toml                      |  1 +
 src/forms/stack.rs              | 83 +++++++++++++++++++++++++--------
 src/helpers/dockerhub.rs        | 22 +++++++--
 src/main.rs                     |  1 +
 src/services/mod.rs             |  2 +-
 tests/custom-stack-payload.json |  2 +-
 7 files changed, 131 insertions(+), 26 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index ff8780bc..4c428877 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,6 +2,31 @@
 # It is not intended for manual editing.
 version = 3
 
+[[package]]
+name = "actix"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cba56612922b907719d4a01cf11c8d5b458e7d3dba946d0435f20f58d6795ed2"
+dependencies = [
+ "actix-macros",
+ "actix-rt",
+ "actix_derive",
+ "bitflags 2.4.1",
+ "bytes",
+ "crossbeam-channel",
+ "futures-core",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+ "log",
+ "once_cell",
+ "parking_lot 0.12.1",
+ "pin-project-lite",
+ "smallvec",
+ "tokio",
+ "tokio-util",
+]
+
 [[package]]
 name = "actix-codec"
 version = "0.5.1"
@@ -211,6 +236,17 @@ dependencies = [
  "pin-project-lite",
 ]
 
+[[package]]
+name = "actix_derive"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c7db3d5a9718568e4cf4a537cfd7070e6e6ff7481510d0237fb529ac850f6d3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
 [[package]]
 name = "addr2line"
 version = "0.21.0"
@@ -898,6 +934,15 @@ dependencies = [
  "cfg-if",
 ]
 
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b"
+dependencies = [
+ "crossbeam-utils",
+]
+
 [[package]]
 name = "crossbeam-queue"
 version = "0.3.11"
@@ -3081,6 +3126,7 @@ dependencies = [
 name = "stacker"
 version = "0.1.0"
 dependencies = [
+ "actix",
  "actix-cors",
  "actix-http",
  "actix-web",
diff --git a/Cargo.toml b/Cargo.toml
index 0fdf1122..ea4b14b9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -54,6 +54,7 @@ futures-lite = "1.13.0"
 clap = { version = "4.4.8", features = ["derive"] }
 brotli = "3.4.0"
 serde_path_to_error = "0.1.14"
+actix = "0.13.1"
 
 [dependencies.sqlx]
 version = "0.6.3"
diff --git a/src/forms/stack.rs b/src/forms/stack.rs
index b799125b..81d0f199 100644
--- a/src/forms/stack.rs
+++ b/src/forms/stack.rs
@@ -4,6 +4,7 @@ use serde_valid::Validate;
 use std::fmt;
 use crate::helpers::{login, docker_image_exists, DockerHubCreds, DockerHubToken};
 use tokio::runtime::Runtime;
+use tokio::runtime::Handle;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Role {
@@ -71,27 +72,69 @@ pub struct Port {
 
 
 fn validate_dockerhub_image(docker_image: &DockerImage) -> Result<(), serde_valid::validation::Error> {
-    println!("validate dockerhub image {:?}", docker_image);
+    println!("validate dockerhub image {:?}", &docker_image);
     tracing::debug!("Validate image at hub.docker.com...");
-
-    let endpoint = "https://hub.docker.com/v2/users/login";
-    let creds = DockerHubCreds {
-        username: docker_image.dockerhub_user.as_ref().unwrap(),
-        password: docker_image.dockerhub_password.as_ref().unwrap()
-    };
-    let client = reqwest::blocking::Client::new();
-    client.post(endpoint)
-        .json(&creds)
-        .send()
-        .map_err(|err| serde_valid::validation::Error::Custom(format!("{:?}", err)))?
-        .json::<DockerHubToken>()
-        .map_err(|err| serde_valid::validation::Error::Custom(format!("{:?}", err)))
-        .and_then(|token| {
-            tracing::debug!("got token {:?}", token);
-            Ok(())
-        })
+    // Ok(())
+
+    let name = docker_image.clone().dockerhub_user.unwrap();
+    let passw = docker_image.clone().dockerhub_password.unwrap();
+    let repo = docker_image.clone().dockerhub_name.unwrap();
+    actix_web::rt::spawn(
+        async move {
+            let result = login(name.as_ref(),passw.as_ref()).await;
+            match result {
+                Ok(token) => {
+                    if token.token.is_some() {
+                        tracing::debug!("we got token {:?}", token);
+                        // while let Ok(true) = docker_image_exists(
+                        //     name.as_ref(),
+                        //     repo.as_ref(),
+                        //     token.clone().token.unwrap()
+                        // ).await {}
+
+                        let _ = docker_image_exists(
+                            name.as_ref(),
+                            repo.as_ref(),
+                            token.clone().token.unwrap()
+                        ).await
+                        .map_err(|err| serde_valid::validation::Error::Custom("Not exists".to_string()))
+                        .map(|_| ());
+                    }
+                },
+                Err(err) => {
+                    tracing::debug!("no token received {}", err);
+                }
+            }
+        }
+    );
+
+    Ok(())
+    // let rt = Runtime::new().unwrap();
+    // // Spawn a blocking function onto the runtime
+    // let join_handle = rt.block_on( async {
+    //     let endpoint = "https://hub.docker.com/v2/users/login";
+    //     let creds = DockerHubCreds {
+    //         username: docker_image.dockerhub_user.as_ref().unwrap(),
+    //         password: docker_image.dockerhub_password.as_ref().unwrap()
+    //     };
+    //     Ok(())
+        // Err(serde_valid::validation::Error::Custom(format!("Error blablablal")))
+        // let client = reqwest::Client::new();
+        // client.post(endpoint)
+        //     .json(&creds)
+        //     .send()
+        //     .await
+        //     .map_err(|err| serde_valid::validation::Error::Custom(format!("{:?}", err)))?
+        //     .json::<DockerHubToken>()
+        //     .await
+        //     .map_err(|err| serde_valid::validation::Error::Custom(format!("{:?}", err)))
+        //     .and_then(|token| {
+        //         tracing::debug!("got token {:?}", token);
+        //         Ok(())
+        //     })
+    // });
+    // join_handle
 }
-
 //
 // fn validate_dockerhub_image(docker_image: &DockerImage) -> Result<(), serde_valid::validation::Error> {
 //
@@ -177,7 +220,7 @@ fn validate_dockerhub_image(docker_image: &DockerImage) -> Result<(), serde_vali
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 #[validate(custom(|dockerhub|validate_dockerhub_image(dockerhub)))]
-pub struct DockerImage {
+pub struct DockerImage  {
     #[validate(min_length = 3)]
     #[validate(max_length = 50)]
     #[validate(pattern = r"^[a-z0-9]+([-_.][a-z0-9]+)*$")]
diff --git a/src/helpers/dockerhub.rs b/src/helpers/dockerhub.rs
index 15bc6c90..39ebca16 100644
--- a/src/helpers/dockerhub.rs
+++ b/src/helpers/dockerhub.rs
@@ -58,8 +58,9 @@ struct TagResult {
 }
 pub async fn login(username: &str, password: &str) -> Result<DockerHubToken, String> {
     let endpoint = "https://hub.docker.com/v2/users/login";
+    tracing::debug!("Login to {} with user {} and passw {}",endpoint, username, password);
     let creds = DockerHubCreds { username, password };
-    let token = reqwest::Client::new()
+    reqwest::Client::new()
         .post(endpoint)
         .json(&creds)
         .send()
@@ -67,11 +68,24 @@ pub async fn login(username: &str, password: &str) -> Result<DockerHubToken, Str
         .map_err(|err| format!("{}", err))?
         .json::<DockerHubToken>()
         .await
-        .map_err(|err| format!("{}", err))?;
-
-    Ok(token)
+        .map_err(|err| format!("{}", err))
+        .map(|token| { token })
 }
 
+// pub async fn login(username: &str, password: &str) -> Result<DockerHubToken, serde_valid::validation::Error> {
+//     let endpoint = "https://hub.docker.com/v2/users/login";
+//     let creds = DockerHubCreds { username, password };
+//     reqwest::Client::new()
+//         .post(endpoint)
+//         .json(&creds)
+//         .send()
+//         .await
+//         .map_err(|err| serde_valid::validation::Error::Custom(format!("{}", err)))?
+//         .json::<DockerHubToken>()
+//         .await
+//         .map_err(|err|  serde_valid::validation::Error::Custom(format!("{}", err)))
+//         .map(|token| { token })
+// }
 
 pub async fn docker_image_exists(user: &str, repo: &str, token: String) -> Result<bool, String> {
     // get repo images
diff --git a/src/main.rs b/src/main.rs
index ebf4987c..d64cc58e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -4,6 +4,7 @@ use stacker::startup::run;
 use stacker::telemetry::{get_subscriber, init_subscriber};
 use std::net::TcpListener;
 
+
 #[actix_web::main]
 async fn main() -> std::io::Result<()> {
     let subscriber = get_subscriber("stacker".into(), "info".into());
diff --git a/src/services/mod.rs b/src/services/mod.rs
index 33c56f43..f61186ff 100644
--- a/src/services/mod.rs
+++ b/src/services/mod.rs
@@ -1,2 +1,2 @@
-mod stack;
+pub mod stack;
 mod rating;
\ No newline at end of file
diff --git a/tests/custom-stack-payload.json b/tests/custom-stack-payload.json
index ba6de202..e85b469c 100644
--- a/tests/custom-stack-payload.json
+++ b/tests/custom-stack-payload.json
@@ -1 +1 @@
-{"commonDomain":"","domainList":{},"region":"fsn1","zone":null,"server":"cx11","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":false,"disk_type":"pd-standart","cloud_token":"****************************","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"networks":["default_network"],"web":[{"name":"MyApp","code":"myapp","domain":"myapp.com","shared_ports":[{"host_port":"80","container_port":""}],"versions":[],"custom":true,"type":"web","network":["default_network"],"restart":"always","timestamp":"2024-01-15T09:47:46.580Z","_id":"lreqrvv81ifrmng39","dockerhub_user":"trydirect","dockerhub_name":"nginx-wafs", "dockerhub_password":  "blabla", "environment":[{"key":"SOMEENVVAR1","value":"SOMEENVVAR_VALUE1"},{"key":"SOMEENVVAR2","value":"SOMEENVVAR_VALUE2"}],"volumes":[]}],"feature":[],"service":[],"servers_count":3,"project_git_url":"info@optimum-web.com","project_name":"Sample2024","custom_stack_code":"sample2024"}}
\ No newline at end of file
+{"commonDomain":"","domainList":{},"region":"fsn1","zone":null,"server":"cx11","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":false,"disk_type":"pd-standart","cloud_token":"****************************","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"networks":["default_network"],"web":[{"name":"MyApp","code":"myapp","domain":"myapp.com","shared_ports":[{"host_port":"80","container_port":"8080"}],"versions":[],"custom":true,"type":"web","network":["default_network"],"restart":"always","timestamp":"2024-01-15T09:47:46.580Z","_id":"lreqrvv81ifrmng39","dockerhub_user":"trydirect","dockerhub_name":"nginx-waf", "dockerhub_password":  "", "environment":[{"key":"SOMEENVVAR1","value":"SOMEENVVAR_VALUE1"},{"key":"SOMEENVVAR2","value":"SOMEENVVAR_VALUE2"}],"volumes":[]}],"feature":[],"service":[],"servers_count":3,"project_git_url":"info@optimum-web.com","project_name":"Sample2024","custom_stack_code":"sample2024"}}
\ No newline at end of file

From 4770f2fa4060b22f9fdfd3d246fb03577e41045c Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Fri, 19 Jan 2024 16:43:22 +0200
Subject: [PATCH 144/284] yet ugly but working validation

---
 src/forms/stack.rs         | 252 ++++++++++---------------------------
 src/helpers/dockerhub.rs   | 201 +++++++++++++++--------------
 src/routes/stack/add.rs    |   4 +-
 src/routes/stack/update.rs |  13 +-
 4 files changed, 185 insertions(+), 285 deletions(-)

diff --git a/src/forms/stack.rs b/src/forms/stack.rs
index 81d0f199..99c4d6bc 100644
--- a/src/forms/stack.rs
+++ b/src/forms/stack.rs
@@ -2,9 +2,10 @@ use serde::{Deserialize, Serialize};
 use serde_json::Value;
 use serde_valid::Validate;
 use std::fmt;
-use crate::helpers::{login, docker_image_exists, DockerHubCreds, DockerHubToken};
-use tokio::runtime::Runtime;
-use tokio::runtime::Handle;
+use serde_valid::validation::error::Format::Default;
+use crate::helpers::dockerhub::{DockerHub, DockerHubCreds, DockerHubToken};
+// use tokio::runtime::Runtime;
+// use tokio::runtime::Handle;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Role {
@@ -39,187 +40,7 @@ pub struct Port {
     pub protocol: Option<String>
 }
 
-// fn validate_dockerhub_image(docker_image: &DockerImage) -> Result<(), serde_valid::validation::Error> {
-//
-//
-//     let rt = Runtime::new().unwrap();
-//
-//     // Spawn a blocking function onto the runtime
-//     rt.block_on(async {
-//         let client = reqwest::Client::new();
-//         let dockerhub_api_url = format!(
-//             "https://hub.docker.com/v2/repositories/{}/{}",
-//             docker_image.dockerhub_user.as_ref().unwrap(),
-//             docker_image.dockerhub_name.as_ref().unwrap()
-//         );
-//
-//         let response = client.get(&dockerhub_api_url)
-//             .send()
-//             .await;
-//
-//         match response {
-//             Ok(resp) => {
-//                 if resp.status().is_success() {
-//                     Ok(())
-//                 } else {
-//                     Err(serde_valid::validation::Error::Custom("Not exists".to_string()))
-//                 }
-//             },
-//             Err(_) => Err(serde_valid::validation::Error::Custom("Not exists".to_string()))
-//         }
-//     })
-// }
-
-
-fn validate_dockerhub_image(docker_image: &DockerImage) -> Result<(), serde_valid::validation::Error> {
-    println!("validate dockerhub image {:?}", &docker_image);
-    tracing::debug!("Validate image at hub.docker.com...");
-    // Ok(())
-
-    let name = docker_image.clone().dockerhub_user.unwrap();
-    let passw = docker_image.clone().dockerhub_password.unwrap();
-    let repo = docker_image.clone().dockerhub_name.unwrap();
-    actix_web::rt::spawn(
-        async move {
-            let result = login(name.as_ref(),passw.as_ref()).await;
-            match result {
-                Ok(token) => {
-                    if token.token.is_some() {
-                        tracing::debug!("we got token {:?}", token);
-                        // while let Ok(true) = docker_image_exists(
-                        //     name.as_ref(),
-                        //     repo.as_ref(),
-                        //     token.clone().token.unwrap()
-                        // ).await {}
-
-                        let _ = docker_image_exists(
-                            name.as_ref(),
-                            repo.as_ref(),
-                            token.clone().token.unwrap()
-                        ).await
-                        .map_err(|err| serde_valid::validation::Error::Custom("Not exists".to_string()))
-                        .map(|_| ());
-                    }
-                },
-                Err(err) => {
-                    tracing::debug!("no token received {}", err);
-                }
-            }
-        }
-    );
-
-    Ok(())
-    // let rt = Runtime::new().unwrap();
-    // // Spawn a blocking function onto the runtime
-    // let join_handle = rt.block_on( async {
-    //     let endpoint = "https://hub.docker.com/v2/users/login";
-    //     let creds = DockerHubCreds {
-    //         username: docker_image.dockerhub_user.as_ref().unwrap(),
-    //         password: docker_image.dockerhub_password.as_ref().unwrap()
-    //     };
-    //     Ok(())
-        // Err(serde_valid::validation::Error::Custom(format!("Error blablablal")))
-        // let client = reqwest::Client::new();
-        // client.post(endpoint)
-        //     .json(&creds)
-        //     .send()
-        //     .await
-        //     .map_err(|err| serde_valid::validation::Error::Custom(format!("{:?}", err)))?
-        //     .json::<DockerHubToken>()
-        //     .await
-        //     .map_err(|err| serde_valid::validation::Error::Custom(format!("{:?}", err)))
-        //     .and_then(|token| {
-        //         tracing::debug!("got token {:?}", token);
-        //         Ok(())
-        //     })
-    // });
-    // join_handle
-}
-//
-// fn validate_dockerhub_image(docker_image: &DockerImage) -> Result<(), serde_valid::validation::Error> {
-//
-//     println!("validate dockerhub image {:?}", docker_image);
-//     tracing::debug!("Validate image at hub.docker.com...");
-//
-//     let endpoint = "https://hub.docker.com/v2/users/login";
-//     let creds = DockerHubCreds {
-//         username: docker_image.dockerhub_user.as_ref().unwrap(),
-//         password: docker_image.dockerhub_password.as_ref().unwrap()
-//     };
-//     reqwest::blocking::Client::new()
-//         .post(endpoint)
-//         .json(&creds)
-//         .send()
-//         .map_err(|err| serde_valid::validation::Error::Custom(format!("{:?}", err)))?
-//         .json::<DockerHubToken>()
-//         .map_err(|err| serde_valid::validation::Error::Custom(format!("{:?}", err)))
-//         .and_then(|token|{
-//             tracing::debug!("got token {:?}", token);
-//             Ok(())
-//         })
-//
-//
-//
-//     // Create the runtime
-//     // let rt = Runtime::new().unwrap();
-//     //
-//     // // Spawn a blocking function onto the runtime
-//     // rt.block_on(async {
-//     //     let result = login(
-//     //         docker_image.dockerhub_user.clone().unwrap_or("".to_string()).as_ref(),
-//     //         docker_image.dockerhub_password.clone().unwrap_or("".to_string()).as_ref()
-//     //     )
-//     //         .await
-//     //         .map_err(|err| serde_valid::validation::Error::Custom(format!("{:?}", err)))?;
-//     //
-//     //     match result.token {
-//     //         None => {
-//     //             return Err(serde_valid::validation::Error::Custom(
-//     //                 "Could not access docker image repository, please check credentials.".to_owned(),
-//     //             ));
-//     //         },
-//     //         Some(tok) => {
-//     //             tracing::debug!("We were able to login hub.docker.com!");
-//     //             docker_image_exists(
-//     //                 docker_image.dockerhub_user.clone().unwrap().as_str(),
-//     //                 docker_image.dockerhub_name.clone().unwrap().as_str(), tok)
-//     //                 .await
-//     //                 .map_err(|err| serde_valid::validation::Error::Custom("Not exists".to_string()))
-//     //                 .map(|_| ())
-//     //         }
-//     //     }
-//     // })
-//
-// }
-
-// fn validate_dockerhub_image(docker_image: &DockerImage) -> Result<(), serde_valid::validation::Error> {
-//
-//     tracing::debug!("validate dockerhub image {:?}", docker_image);
-//     let endpoint = "https://hub.docker.com/v2/users/login";
-//     let creds = DockerHubCreds {
-//         username: docker_image.dockerhub_user.as_ref().unwrap(),
-//         password: docker_image.dockerhub_password.as_ref().unwrap()
-//     };
-//     reqwest::blocking::Client::new()
-//         .post(endpoint)
-//         .json(&creds)
-//         .send()
-//         .map_err(|err| serde_valid::validation::Error::Custom(format!("{:?}", err)))?
-//         .json::<DockerHubToken>()
-//         .map_err(|err| serde_valid::validation::Error::Custom(format!("{:?}", err)))
-//         .and_then(|token|{
-//             docker_image_exists(
-//                 docker_image.dockerhub_user.clone().unwrap().as_str(),
-//                 docker_image.dockerhub_name.clone().unwrap().as_str(),
-//                 token
-//             )
-//                 .map_err(|err| serde_valid::validation::Error::Custom("Not exists".to_string()))
-//                 .map(|_| ())
-//         })
-// }
-
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-#[validate(custom(|dockerhub|validate_dockerhub_image(dockerhub)))]
 pub struct DockerImage  {
     #[validate(min_length = 3)]
     #[validate(max_length = 50)]
@@ -252,6 +73,28 @@ impl fmt::Display for DockerImage {
 }
 
 
+impl DockerImage {
+
+    async fn is_active(&self) -> Result<bool, String> {
+        tracing::debug!("Validate image at DockerHub {:?}", &self);
+        let mut dockerhub = DockerHub::build();
+
+        dockerhub
+            .login(
+                self.dockerhub_user.clone().unwrap_or("".to_string()).as_ref(),
+                self.dockerhub_password.clone().unwrap().as_ref(),
+            )
+            .await
+            .set_image(self.dockerhub_image.clone().unwrap_or(String::from("")).as_str())
+            .await
+            .set_repos(self.dockerhub_name.clone().unwrap_or(String::from("")).as_str())
+            .await
+            .is_active()
+            .await
+    }
+}
+
+
 impl AsRef<DockerImage> for App {
     fn as_ref(&self) -> &DockerImage {
         &self.docker_image
@@ -304,6 +147,48 @@ pub struct StackForm {
     pub custom: Custom,
 }
 
+impl StackForm {
+
+    pub async fn is_readable_docker_image(&self) -> Result<bool, String> {
+
+        let mut is_active = true;
+        for app in &self.custom.web {
+           if !app.app.docker_image.is_active().await? {
+               is_active = false;
+               break;
+           }
+        }
+
+        // self.custom.service
+        //     .into_iter()
+        //     .map(|item| {
+        //         item.iter()
+        //             .any(|item| {
+        //                 item.app.docker_image.is_active()?
+        //             })
+        //             .collect()
+        //     }).collect();
+        //
+        if let Some(service) = &self.custom.service {
+            for app in service {
+                if !app.app.docker_image.is_active().await? {
+                    is_active = false;
+                    break;
+                }
+            }
+        }
+
+        if let Some(features) = &self.custom.feature {
+            for app in features {
+                if !app.app.docker_image.is_active().await? {
+                    is_active = false;
+                    break;
+                }
+            }
+        }
+        Ok(is_active)
+    }
+}
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 #[serde(rename_all = "snake_case")]
 pub struct StackPayload {
@@ -448,6 +333,7 @@ pub struct App {
     pub shared_ports: Option<Vec<Port>>,
 }
 
+
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct EnvVar {
     pub(crate) key: String,
diff --git a/src/helpers/dockerhub.rs b/src/helpers/dockerhub.rs
index 39ebca16..70a5e7a5 100644
--- a/src/helpers/dockerhub.rs
+++ b/src/helpers/dockerhub.rs
@@ -1,6 +1,5 @@
 use serde_derive::{Deserialize, Serialize};
 use serde_valid::Validate;
-// use tracing_subscriber::fmt::format;
 use serde_json::Value;
 
 
@@ -10,7 +9,7 @@ pub struct DockerHubToken {
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct DockerHubCreds<'a>  {
+pub struct DockerHubCreds<'a> {
     pub(crate) username: &'a str,
     pub(crate) password: &'a str
 }
@@ -56,103 +55,111 @@ struct TagResult {
     previous: Value,
     results: Vec<Tag>
 }
-pub async fn login(username: &str, password: &str) -> Result<DockerHubToken, String> {
-    let endpoint = "https://hub.docker.com/v2/users/login";
-    tracing::debug!("Login to {} with user {} and passw {}",endpoint, username, password);
-    let creds = DockerHubCreds { username, password };
-    reqwest::Client::new()
-        .post(endpoint)
-        .json(&creds)
-        .send()
-        .await
-        .map_err(|err| format!("{}", err))?
-        .json::<DockerHubToken>()
-        .await
-        .map_err(|err| format!("{}", err))
-        .map(|token| { token })
+
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Validate)]
+pub(crate) struct DockerHub<'a> {
+    pub(crate) creds: DockerHubCreds<'a>,
+    pub(crate) repos: String,
+    pub(crate) token: DockerHubToken,
+    pub(crate) image: String,
+}
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Validate)]
+pub(crate) struct DockerHubBuilder<'a> {
+    pub(crate) creds: DockerHubCreds<'a>,
+    pub(crate) repos: &'a str,
+    pub(crate) token: DockerHubToken,
+    pub(crate) image: &'a str,
 }
 
-// pub async fn login(username: &str, password: &str) -> Result<DockerHubToken, serde_valid::validation::Error> {
-//     let endpoint = "https://hub.docker.com/v2/users/login";
-//     let creds = DockerHubCreds { username, password };
-//     reqwest::Client::new()
-//         .post(endpoint)
-//         .json(&creds)
-//         .send()
-//         .await
-//         .map_err(|err| serde_valid::validation::Error::Custom(format!("{}", err)))?
-//         .json::<DockerHubToken>()
-//         .await
-//         .map_err(|err|  serde_valid::validation::Error::Custom(format!("{}", err)))
-//         .map(|token| { token })
-// }
-
-pub async fn docker_image_exists(user: &str, repo: &str, token: String) -> Result<bool, String> {
-    // get repo images
-    let tags_url = format!("https://hub.docker.com/v2/namespaces/{}/repositories/{}/tags",
-        user, repo);
-
-    let tags = reqwest::Client::new()
-        .get(tags_url)
-        .header("Accept", "application/json")
-        .bearer_auth(token)
-        .send()
-        .await
-        .map_err(|err| format!("{}", err))?
-        .json::<TagResult>()
-        // .json::<serde_json::Value>()
-        .await
-        .map_err(|err| format!("{}", err))?;
-
-    // println!("tags count: {:?}", tags.count);
-
-    if tags.count > 0 {
-        // let's find at least one active tag
-        let active = tags.results
-            .into_iter()
-            .any(|tag| tag.tag_status.contains("active") );
-        Ok(active)
-    } else {
-        Err(String::from("There were no active tags found in this repository"))
+impl<'a> DockerHubBuilder<'a> {
+
+    pub async fn set_repos(&'a mut self, repo_name: &'a str) -> &'a mut Self {
+        self.repos = repo_name;
+        self
+    }
+
+    pub async fn set_image(&'a mut self, image: &'a str) -> &'a mut Self {
+        self.image = image;
+        self
+    }
+    pub async fn login(&'a mut self, username: &'a str, password: &'a str) -> &'a mut Self {
+        let endpoint = "https://hub.docker.com/v2/users/login";
+
+        self.creds = DockerHubCreds {
+            username: username,
+            password: password
+        };
+
+        if self.creds.password.is_empty() {
+            tracing::debug!("DockerHub credentials were not provided, login not required/image considered public");
+            return self;
+        }
+
+        let response = reqwest::Client::new()
+            .post(endpoint)
+            .json(&self.creds)
+            .send()
+            .await
+            .map_err(|err| format!("{:?}", err))
+            .unwrap()
+            .json::<DockerHubToken>()
+            .await
+            .map_err(|err| format!("{:?}", err))
+            .map(|token| {
+                self.token = token;
+            });
+
+        tracing::debug!("Login response was: {:?}", response);
+        self
     }
+
+    pub async fn is_active(&'a self) -> Result<bool, String> {
+        // get repo images
+        let tags_url = format!("https://hub.docker.com/v2/namespaces/{}/repositories/{}/tags",
+                               &self.creds.username, &self.repos);
+
+        let mut client = reqwest::Client::new()
+            .get(tags_url)
+            .header("Accept", "application/json");
+
+        client = match self.token.token.as_ref() {
+            Some(token) => {
+                if !token.is_empty() {
+                    client = client.bearer_auth(token);
+                }
+                client
+            },
+            None => {
+                client
+            }
+        };
+
+        client
+            .send()
+            .await
+            .map_err(|err| format!("{}", err))?
+            .json::<TagResult>()
+            .await
+            .map_err(|err| format!("{}", err))
+            .map(|tags| {
+                if tags.count > 0 {
+                    // let's find at least one active tag
+                    let active = tags.results
+                        .into_iter()
+                        .any(|tag| tag.tag_status.contains("active") );
+                    active
+                } else {
+                    false
+                }
+            })
+    }
+
 }
 
-// pub fn docker_image_exists(user: &str, repo: &str, token: DockerHubToken) -> Result<bool, serde_valid::validation::Error> {
-//     // get repo images
-//     let tags_url = format!("https://hub.docker.com/v2/namespaces/{}/repositories/{}/tags",
-//                            user, repo);
-//     //
-//     // let tags = reqwest::Client::new()
-//     //     .get(tags_url)
-//     //     .header("Accept", "application/json")
-//     //     .bearer_auth(token)
-//     //     .send()
-//     //     .await
-//     //     .map_err(|err| format!("{}", err))?
-//     //     .json::<TagResult>()
-//     //     // .json::<serde_json::Value>()
-//     //     .await
-//     //     .map_err(|err| format!("{}", err))?;
-//
-//     reqwest::blocking::Client::new()
-//         .get(tags_url)
-//         .header("Accept", "application/json")
-//         .bearer_auth(token.token.unwrap())
-//         .send()
-//         .map_err(|err| serde_valid::validation::Error::Custom(format!("{:?}", err)))?
-//         .json::<TagResult>()
-//         .map_err(|err| serde_valid::validation::Error::Custom(format!("{:?}", err)))
-//         .and_then(|tags|{
-//             println!("tags count: {:?}", tags.count);
-//             if tags.count > 0 {
-//                 // let's find at least one active tag
-//                 let active = tags.results
-//                     .into_iter()
-//                     .any(|tag| tag.tag_status.contains("active") );
-//                 Ok(active)
-//             } else {
-//                 Err(serde_valid::validation::Error::Custom("There were no active tags found in this repository".to_string()))
-//             }
-//         })
-//
-// }
+impl<'a> DockerHub<'a>
+{
+    pub fn build() -> DockerHubBuilder<'a> {
+        DockerHubBuilder::default()
+    }
+}
\ No newline at end of file
diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs
index a399f017..5d0fffdc 100644
--- a/src/routes/stack/add.rs
+++ b/src/routes/stack/add.rs
@@ -66,13 +66,13 @@ async fn body_into_form(body: Bytes) -> Result<StackForm, Error> {
             JsonResponse::<StackForm>::build().bad_request(msg)
         })
         .and_then(|form: StackForm| {
-
-
             tracing::debug!("Validating form ...");
+
             if !form.validate().is_ok() {
                 let errors = form.validate().unwrap_err();
                 let err_msg = format!("Invalid data received {:?}", &errors.to_string());
                 tracing::debug!(err_msg);
+
                 return Err(JsonResponse::<models::Stack>::build().bad_request(errors.to_string()));
             }
 
diff --git a/src/routes/stack/update.rs b/src/routes/stack/update.rs
index e62dd94d..c5a3afc0 100644
--- a/src/routes/stack/update.rs
+++ b/src/routes/stack/update.rs
@@ -62,8 +62,7 @@ pub async fn update(
 
     if !form.validate().is_ok() {
         let errors = form.validate().unwrap_err();
-        let err_msg = format!("Invalid data received {:?}", &errors.to_string());
-        tracing::debug!(err_msg);
+        tracing::debug!("Invalid data received {:?}", &errors.to_string());
 
         return Err(JsonResponse::<models::Stack>::build().bad_request(errors.to_string()));
     }
@@ -74,7 +73,15 @@ pub async fn update(
         form.region
     );
     let query_span = tracing::info_span!("Update stack details in db.");
-    let body: Value = match serde_json::to_value::<StackForm>(form.into_inner()) {
+
+    let form_inner = form.into_inner();
+
+    if !form_inner.is_readable_docker_image().await.is_ok() {
+
+        return Err(JsonResponse::<models::Stack>::build().bad_request("Can not access docker image"));
+    }
+
+    let body: Value = match serde_json::to_value::<StackForm>(form_inner) {
         Ok(body) => body,
         Err(err) => {
             tracing::error!("Request_id {} error unwrap body {:?}", request_id, err);

From 0e93e1fa727a8c97a051c646bd7f9668da67725b Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Fri, 19 Jan 2024 17:32:18 +0200
Subject: [PATCH 145/284] issue-auth removed compressor

---
 Cargo.lock                 |  1 -
 Cargo.toml                 |  1 -
 src/helpers/compressor.rs  | 20 --------------------
 src/helpers/mod.rs         |  2 --
 src/routes/stack/deploy.rs |  1 -
 5 files changed, 25 deletions(-)
 delete mode 100644 src/helpers/compressor.rs

diff --git a/Cargo.lock b/Cargo.lock
index aae6152c..ea10ab09 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3138,7 +3138,6 @@ dependencies = [
  "actix-http",
  "actix-web",
  "actix-web-httpauth",
- "brotli",
  "chrono",
  "clap",
  "config",
diff --git a/Cargo.toml b/Cargo.toml
index 649ae237..e13da269 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -52,7 +52,6 @@ serde_yaml = "0.9"
 lapin = { version = "2.3.1", features = ["serde_json"] }
 futures-lite = "1.13.0"
 clap = { version = "4.4.8", features = ["derive"] }
-brotli = "3.4.0"
 serde_path_to_error = "0.1.14"
 deadpool-lapin = "0.11.0"
 docker-compose-types = "0.7.0"
diff --git a/src/helpers/compressor.rs b/src/helpers/compressor.rs
deleted file mode 100644
index 8061329c..00000000
--- a/src/helpers/compressor.rs
+++ /dev/null
@@ -1,20 +0,0 @@
-use brotli::{CompressorWriter, Decompressor};
-use std::io::{Read, Write};
-
-pub fn compress(input: &str) -> Vec<u8> {
-    let mut compressed = Vec::new();
-    let mut compressor = CompressorWriter::new(
-        &mut compressed, 4096, 11, 22
-    );
-    compressor.write_all(input.as_bytes()).unwrap();
-    compressor.flush().unwrap();
-    drop(compressor);
-    compressed
-}
-
-pub fn decompress(input: &[u8]) -> String {
-    let mut decompressed = String::new();
-    let mut decompressor = Decompressor::new(input, 4096);
-    decompressor.read_to_string(&mut decompressed).unwrap();
-    decompressed
-}
diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs
index fd061b9c..6d3b1699 100644
--- a/src/helpers/mod.rs
+++ b/src/helpers/mod.rs
@@ -2,8 +2,6 @@ pub mod client;
 pub(crate) mod json;
 mod mq_manager;
 pub(crate) mod stack;
-pub(crate) mod compressor;
 
 pub use json::*;
 pub use mq_manager::MqManager;
-pub use compressor::*;
diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs
index ac7bc83d..71d459ff 100644
--- a/src/routes/stack/deploy.rs
+++ b/src/routes/stack/deploy.rs
@@ -8,7 +8,6 @@ use actix_web::{post, web, web::Data, Responder, Result};
 use lapin::publisher_confirm::Confirmation;
 use sqlx::PgPool;
 use std::sync::Arc;
-use crate::helpers::compressor::compress;
 
 #[tracing::instrument(name = "Deploy for every user. Admin endpoint")]
 #[post("/{id}/deploy")]

From 1f0449ac8086b002c51a5aa18716a180b5b2c6ec Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 20 Jan 2024 19:41:33 +0200
Subject: [PATCH 146/284] issue-auth stack/update refactor

---
 Cargo.lock                       | 1565 +++++++++++++++++++++++++++---
 Cargo.toml                       |    1 +
 src/db/stack.rs                  |   37 +
 src/middleware/access_control.rs |    5 +
 src/middleware/mod.rs            |    1 +
 src/routes/stack/compose.rs      |   16 +-
 src/routes/stack/get.rs          |    8 +-
 src/routes/stack/update.rs       |  111 +--
 8 files changed, 1497 insertions(+), 247 deletions(-)
 create mode 100644 src/middleware/access_control.rs

diff --git a/Cargo.lock b/Cargo.lock
index ea10ab09..39301be6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,6 +2,35 @@
 # It is not intended for manual editing.
 version = 3
 
+[[package]]
+name = "actix-casbin-auth"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "feb9c094ecc5e0a561a44aee0c0de87be31c524b26f5096a8fde5c8de2eacec3"
+dependencies = [
+ "actix-service 1.0.6",
+ "actix-web 3.3.3",
+ "async-std",
+ "casbin",
+ "futures",
+]
+
+[[package]]
+name = "actix-codec"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78d1833b3838dbe990df0f1f87baf640cf6146e898166afe401839d1b001e570"
+dependencies = [
+ "bitflags 1.3.2",
+ "bytes 0.5.6",
+ "futures-core",
+ "futures-sink",
+ "log",
+ "pin-project 0.4.30",
+ "tokio 0.2.25",
+ "tokio-util 0.3.1",
+]
+
 [[package]]
 name = "actix-codec"
 version = "0.5.1"
@@ -9,24 +38,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "617a8268e3537fe1d8c9ead925fca49ef6400927ee7bc26750e90ecee14ce4b8"
 dependencies = [
  "bitflags 1.3.2",
- "bytes",
+ "bytes 1.5.0",
  "futures-core",
  "futures-sink",
  "memchr",
- "pin-project-lite",
- "tokio",
- "tokio-util",
+ "pin-project-lite 0.2.13",
+ "tokio 1.34.0",
+ "tokio-util 0.7.10",
  "tracing",
 ]
 
+[[package]]
+name = "actix-connect"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "177837a10863f15ba8d3ae3ec12fac1099099529ed20083a27fdfe247381d0dc"
+dependencies = [
+ "actix-codec 0.3.0",
+ "actix-rt 1.1.1",
+ "actix-service 1.0.6",
+ "actix-utils 2.0.0",
+ "derive_more",
+ "either",
+ "futures-util",
+ "http",
+ "log",
+ "trust-dns-proto",
+ "trust-dns-resolver",
+]
+
 [[package]]
 name = "actix-cors"
 version = "0.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b340e9cfa5b08690aae90fb61beb44e9b06f44fe3d0f93781aaa58cfba86245e"
 dependencies = [
- "actix-utils",
- "actix-web",
+ "actix-utils 3.0.1",
+ "actix-web 4.4.0",
  "derive_more",
  "futures-util",
  "log",
@@ -34,45 +82,100 @@ dependencies = [
  "smallvec",
 ]
 
+[[package]]
+name = "actix-http"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2be6b66b62a794a8e6d366ac9415bb7d475ffd1e9f4671f38c1d8a8a5df950b3"
+dependencies = [
+ "actix-codec 0.3.0",
+ "actix-connect",
+ "actix-rt 1.1.1",
+ "actix-service 1.0.6",
+ "actix-threadpool",
+ "actix-utils 2.0.0",
+ "base64 0.13.1",
+ "bitflags 1.3.2",
+ "bytes 0.5.6",
+ "cookie 0.14.4",
+ "copyless",
+ "derive_more",
+ "either",
+ "encoding_rs",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "fxhash",
+ "h2 0.2.7",
+ "http",
+ "httparse",
+ "indexmap 1.9.3",
+ "itoa 0.4.8",
+ "language-tags 0.2.2",
+ "lazy_static",
+ "log",
+ "mime",
+ "percent-encoding",
+ "pin-project 1.1.3",
+ "rand 0.7.3",
+ "regex",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "sha-1",
+ "slab",
+ "time 0.2.27",
+]
+
 [[package]]
 name = "actix-http"
 version = "3.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a92ef85799cba03f76e4f7c10f533e66d87c9a7e7055f3391f09000ad8351bc9"
 dependencies = [
- "actix-codec",
- "actix-rt",
- "actix-service",
- "actix-utils",
+ "actix-codec 0.5.1",
+ "actix-rt 2.9.0",
+ "actix-service 2.0.2",
+ "actix-utils 3.0.1",
  "ahash 0.8.6",
  "base64 0.21.5",
  "bitflags 2.4.1",
  "brotli",
- "bytes",
+ "bytes 1.5.0",
  "bytestring",
  "derive_more",
  "encoding_rs",
  "flate2",
  "futures-core",
- "h2",
+ "h2 0.3.22",
  "http",
  "httparse",
  "httpdate",
- "itoa",
- "language-tags",
+ "itoa 1.0.9",
+ "language-tags 0.3.2",
  "local-channel",
  "mime",
  "percent-encoding",
- "pin-project-lite",
+ "pin-project-lite 0.2.13",
  "rand 0.8.5",
- "sha1",
+ "sha1 0.10.6",
  "smallvec",
- "tokio",
- "tokio-util",
+ "tokio 1.34.0",
+ "tokio-util 0.7.10",
  "tracing",
  "zstd",
 ]
 
+[[package]]
+name = "actix-macros"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4ca8ce00b267af8ccebbd647de0d61e0674b6e61185cc7a592ff88772bed655"
+dependencies = [
+ "quote",
+ "syn 1.0.109",
+]
+
 [[package]]
 name = "actix-macros"
 version = "0.2.4"
@@ -83,6 +186,19 @@ dependencies = [
  "syn 2.0.39",
 ]
 
+[[package]]
+name = "actix-router"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ad299af73649e1fc893e333ccf86f377751eb95ff875d095131574c6f43452c"
+dependencies = [
+ "bytestring",
+ "http",
+ "log",
+ "regex",
+ "serde",
+]
+
 [[package]]
 name = "actix-router"
 version = "0.5.1"
@@ -96,6 +212,21 @@ dependencies = [
  "tracing",
 ]
 
+[[package]]
+name = "actix-rt"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "143fcc2912e0d1de2bcf4e2f720d2a60c28652ab4179685a1ee159e0fb3db227"
+dependencies = [
+ "actix-macros 0.1.3",
+ "actix-threadpool",
+ "copyless",
+ "futures-channel",
+ "futures-util",
+ "smallvec",
+ "tokio 0.2.25",
+]
+
 [[package]]
 name = "actix-rt"
 version = "2.9.0"
@@ -103,7 +234,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "28f32d40287d3f402ae0028a9d54bef51af15c8769492826a69d28f81893151d"
 dependencies = [
  "futures-core",
- "tokio",
+ "tokio 1.34.0",
+]
+
+[[package]]
+name = "actix-server"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45407e6e672ca24784baa667c5d32ef109ccdd8d5e0b5ebb9ef8a67f4dfb708e"
+dependencies = [
+ "actix-codec 0.3.0",
+ "actix-rt 1.1.1",
+ "actix-service 1.0.6",
+ "actix-utils 2.0.0",
+ "futures-channel",
+ "futures-util",
+ "log",
+ "mio 0.6.23",
+ "mio-uds",
+ "num_cpus",
+ "slab",
+ "socket2 0.3.19",
 ]
 
 [[package]]
@@ -112,17 +263,27 @@ version = "2.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3eb13e7eef0423ea6eab0e59f6c72e7cb46d33691ad56a726b3cd07ddec2c2d4"
 dependencies = [
- "actix-rt",
- "actix-service",
- "actix-utils",
+ "actix-rt 2.9.0",
+ "actix-service 2.0.2",
+ "actix-utils 3.0.1",
  "futures-core",
  "futures-util",
- "mio",
+ "mio 0.8.9",
  "socket2 0.5.5",
- "tokio",
+ "tokio 1.34.0",
  "tracing",
 ]
 
+[[package]]
+name = "actix-service"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0052435d581b5be835d11f4eb3bce417c8af18d87ddf8ace99f8e67e595882bb"
+dependencies = [
+ "futures-util",
+ "pin-project 0.4.30",
+]
+
 [[package]]
 name = "actix-service"
 version = "2.0.2"
@@ -131,7 +292,68 @@ checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a"
 dependencies = [
  "futures-core",
  "paste",
- "pin-project-lite",
+ "pin-project-lite 0.2.13",
+]
+
+[[package]]
+name = "actix-testing"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47239ca38799ab74ee6a8a94d1ce857014b2ac36f242f70f3f75a66f691e791c"
+dependencies = [
+ "actix-macros 0.1.3",
+ "actix-rt 1.1.1",
+ "actix-server 1.0.4",
+ "actix-service 1.0.6",
+ "log",
+ "socket2 0.3.19",
+]
+
+[[package]]
+name = "actix-threadpool"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d209f04d002854b9afd3743032a27b066158817965bf5d036824d19ac2cc0e30"
+dependencies = [
+ "derive_more",
+ "futures-channel",
+ "lazy_static",
+ "log",
+ "num_cpus",
+ "parking_lot 0.11.2",
+ "threadpool",
+]
+
+[[package]]
+name = "actix-tls"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24789b7d7361cf5503a504ebe1c10806896f61e96eca9a7350e23001aca715fb"
+dependencies = [
+ "actix-codec 0.3.0",
+ "actix-service 1.0.6",
+ "actix-utils 2.0.0",
+ "futures-util",
+]
+
+[[package]]
+name = "actix-utils"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e9022dec56632d1d7979e59af14f0597a28a830a9c1c7fec8b2327eb9f16b5a"
+dependencies = [
+ "actix-codec 0.3.0",
+ "actix-rt 1.1.1",
+ "actix-service 1.0.6",
+ "bitflags 1.3.2",
+ "bytes 0.5.6",
+ "either",
+ "futures-channel",
+ "futures-sink",
+ "futures-util",
+ "log",
+ "pin-project 0.4.30",
+ "slab",
 ]
 
 [[package]]
@@ -141,7 +363,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8"
 dependencies = [
  "local-waker",
- "pin-project-lite",
+ "pin-project-lite 0.2.13",
+]
+
+[[package]]
+name = "actix-web"
+version = "3.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6534a126df581caf443ba2751cab42092c89b3f1d06a9d829b1e17edfe3e277"
+dependencies = [
+ "actix-codec 0.3.0",
+ "actix-http 2.2.2",
+ "actix-macros 0.1.3",
+ "actix-router 0.2.7",
+ "actix-rt 1.1.1",
+ "actix-server 1.0.4",
+ "actix-service 1.0.6",
+ "actix-testing",
+ "actix-threadpool",
+ "actix-tls",
+ "actix-utils 2.0.0",
+ "actix-web-codegen 0.4.0",
+ "awc",
+ "bytes 0.5.6",
+ "derive_more",
+ "encoding_rs",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "fxhash",
+ "log",
+ "mime",
+ "pin-project 1.1.3",
+ "regex",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "socket2 0.3.19",
+ "time 0.2.27",
+ "tinyvec",
+ "url",
 ]
 
 [[package]]
@@ -150,30 +411,30 @@ version = "4.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0e4a5b5e29603ca8c94a77c65cf874718ceb60292c5a5c3e5f4ace041af462b9"
 dependencies = [
- "actix-codec",
- "actix-http",
- "actix-macros",
- "actix-router",
- "actix-rt",
- "actix-server",
- "actix-service",
- "actix-utils",
- "actix-web-codegen",
+ "actix-codec 0.5.1",
+ "actix-http 3.4.0",
+ "actix-macros 0.2.4",
+ "actix-router 0.5.1",
+ "actix-rt 2.9.0",
+ "actix-server 2.3.0",
+ "actix-service 2.0.2",
+ "actix-utils 3.0.1",
+ "actix-web-codegen 4.2.2",
  "ahash 0.8.6",
- "bytes",
+ "bytes 1.5.0",
  "bytestring",
- "cfg-if",
- "cookie",
+ "cfg-if 1.0.0",
+ "cookie 0.16.2",
  "derive_more",
  "encoding_rs",
  "futures-core",
  "futures-util",
- "itoa",
- "language-tags",
+ "itoa 1.0.9",
+ "language-tags 0.3.2",
  "log",
  "mime",
  "once_cell",
- "pin-project-lite",
+ "pin-project-lite 0.2.13",
  "regex",
  "serde",
  "serde_json",
@@ -184,13 +445,24 @@ dependencies = [
  "url",
 ]
 
+[[package]]
+name = "actix-web-codegen"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad26f77093333e0e7c6ffe54ebe3582d908a104e448723eec6d43d08b07143fb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
 [[package]]
 name = "actix-web-codegen"
 version = "4.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "eb1f50ebbb30eca122b188319a4398b3f7bb4a8cdf50ecfb73bfc6a3c3ce54f5"
 dependencies = [
- "actix-router",
+ "actix-router 0.5.1",
  "proc-macro2",
  "quote",
  "syn 2.0.39",
@@ -202,13 +474,13 @@ version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1d613edf08a42ccc6864c941d30fe14e1b676a77d16f1dbadc1174d065a0a775"
 dependencies = [
- "actix-utils",
- "actix-web",
+ "actix-utils 3.0.1",
+ "actix-web 4.4.0",
  "base64 0.21.5",
  "futures-core",
  "futures-util",
  "log",
- "pin-project-lite",
+ "pin-project-lite 0.2.13",
 ]
 
 [[package]]
@@ -243,7 +515,8 @@ version = "0.8.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
+ "const-random",
  "getrandom 0.2.11",
  "once_cell",
  "version_check",
@@ -428,7 +701,7 @@ dependencies = [
  "event-listener 4.0.0",
  "event-listener-strategy",
  "futures-core",
- "pin-project-lite",
+ "pin-project-lite 0.2.13",
 ]
 
 [[package]]
@@ -479,7 +752,7 @@ checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af"
 dependencies = [
  "async-lock 2.8.0",
  "autocfg",
- "cfg-if",
+ "cfg-if 1.0.0",
  "concurrent-queue",
  "futures-lite 1.13.0",
  "log",
@@ -498,7 +771,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d6d3b15875ba253d1110c740755e246537483f152fa334f91abd7fe84c88b3ff"
 dependencies = [
  "async-lock 3.1.2",
- "cfg-if",
+ "cfg-if 1.0.0",
  "concurrent-queue",
  "futures-io",
  "futures-lite 2.0.1",
@@ -527,7 +800,7 @@ checksum = "dea8b3453dd7cc96711834b75400d671b73e3656975fa68d9f277163b7f7e316"
 dependencies = [
  "event-listener 4.0.0",
  "event-listener-strategy",
- "pin-project-lite",
+ "pin-project-lite 0.2.13",
 ]
 
 [[package]]
@@ -542,6 +815,32 @@ dependencies = [
  "reactor-trait",
 ]
 
+[[package]]
+name = "async-std"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d"
+dependencies = [
+ "async-channel 1.9.0",
+ "async-global-executor",
+ "async-io 1.13.0",
+ "async-lock 2.8.0",
+ "crossbeam-utils",
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-lite 1.13.0",
+ "gloo-timers",
+ "kv-log-macro",
+ "log",
+ "memchr",
+ "once_cell",
+ "pin-project-lite 0.2.13",
+ "pin-utils",
+ "slab",
+ "wasm-bindgen-futures",
+]
+
 [[package]]
 name = "async-task"
 version = "4.5.0"
@@ -580,6 +879,30 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
+[[package]]
+name = "awc"
+version = "2.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b381e490e7b0cfc37ebc54079b0413d8093ef43d14a4e4747083f7fa47a9e691"
+dependencies = [
+ "actix-codec 0.3.0",
+ "actix-http 2.2.2",
+ "actix-rt 1.1.1",
+ "actix-service 1.0.6",
+ "base64 0.13.1",
+ "bytes 0.5.6",
+ "cfg-if 1.0.0",
+ "derive_more",
+ "futures-core",
+ "log",
+ "mime",
+ "percent-encoding",
+ "rand 0.7.3",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+]
+
 [[package]]
 name = "backtrace"
 version = "0.3.69"
@@ -588,13 +911,19 @@ checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
 dependencies = [
  "addr2line",
  "cc",
- "cfg-if",
+ "cfg-if 1.0.0",
  "libc",
  "miniz_oxide",
  "object",
  "rustc-demangle",
 ]
 
+[[package]]
+name = "base-x"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270"
+
 [[package]]
 name = "base64"
 version = "0.13.1"
@@ -619,6 +948,15 @@ version = "2.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
 
+[[package]]
+name = "block-buffer"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
+dependencies = [
+ "generic-array",
+]
+
 [[package]]
 name = "block-buffer"
 version = "0.10.4"
@@ -680,12 +1018,24 @@ version = "3.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
 
+[[package]]
+name = "bytecount"
+version = "0.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205"
+
 [[package]]
 name = "byteorder"
 version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
 
+[[package]]
+name = "bytes"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
+
 [[package]]
 name = "bytes"
 version = "1.5.0"
@@ -698,7 +1048,59 @@ version = "1.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "74d80203ea6b29df88012294f62733de21cfeab47f17b41af3a38bc30a03ee72"
 dependencies = [
- "bytes",
+ "bytes 1.5.0",
+]
+
+[[package]]
+name = "camino"
+version = "1.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cargo-platform"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ceed8ef69d8518a5dda55c07425450b58a4e1946f4951eab6d7191ee86c2443d"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cargo_metadata"
+version = "0.14.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa"
+dependencies = [
+ "camino",
+ "cargo-platform",
+ "semver 1.0.20",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "casbin"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53fd06051bc34aa7ee753647dbd36506fa9f84026993d9871e65e0ee71ac3632"
+dependencies = [
+ "async-std",
+ "async-trait",
+ "fixedbitset",
+ "getrandom 0.2.11",
+ "mini-moka",
+ "once_cell",
+ "parking_lot 0.12.1",
+ "petgraph",
+ "regex",
+ "rhai",
+ "ritelinked",
+ "serde",
+ "thiserror",
 ]
 
 [[package]]
@@ -720,6 +1122,12 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+
 [[package]]
 name = "cfg-if"
 version = "1.0.0"
@@ -826,12 +1234,49 @@ dependencies = [
  "yaml-rust",
 ]
 
+[[package]]
+name = "const-random"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aaf16c9c2c612020bcfd042e170f6e32de9b9d75adb5277cdbbd2e2c8c8299a"
+dependencies = [
+ "const-random-macro",
+]
+
+[[package]]
+name = "const-random-macro"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
+dependencies = [
+ "getrandom 0.2.11",
+ "once_cell",
+ "tiny-keccak",
+]
+
+[[package]]
+name = "const_fn"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935"
+
 [[package]]
 name = "convert_case"
 version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
 
+[[package]]
+name = "cookie"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951"
+dependencies = [
+ "percent-encoding",
+ "time 0.2.27",
+ "version_check",
+]
+
 [[package]]
 name = "cookie"
 version = "0.16.2"
@@ -849,6 +1294,12 @@ version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b"
 
+[[package]]
+name = "copyless"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536"
+
 [[package]]
 name = "core-foundation"
 version = "0.9.3"
@@ -895,7 +1346,17 @@ version = "1.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
+dependencies = [
+ "cfg-if 1.0.0",
+ "crossbeam-utils",
 ]
 
 [[package]]
@@ -904,7 +1365,7 @@ version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "crossbeam-utils",
 ]
 
@@ -914,9 +1375,15 @@ version = "0.8.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
 ]
 
+[[package]]
+name = "crunchy"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
+
 [[package]]
 name = "crypto-common"
 version = "0.1.6"
@@ -962,6 +1429,19 @@ dependencies = [
  "syn 1.0.109",
 ]
 
+[[package]]
+name = "dashmap"
+version = "5.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
+dependencies = [
+ "cfg-if 1.0.0",
+ "hashbrown 0.14.2",
+ "lock_api",
+ "once_cell",
+ "parking_lot_core 0.9.9",
+]
+
 [[package]]
 name = "deadpool"
 version = "0.9.5"
@@ -972,7 +1452,7 @@ dependencies = [
  "deadpool-runtime",
  "num_cpus",
  "retain_mut",
- "tokio",
+ "tokio 1.34.0",
 ]
 
 [[package]]
@@ -984,7 +1464,7 @@ dependencies = [
  "async-trait",
  "deadpool-runtime",
  "num_cpus",
- "tokio",
+ "tokio 1.34.0",
 ]
 
 [[package]]
@@ -1004,7 +1484,7 @@ version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "63dfa964fe2a66f3fde91fc70b267fe193d822c7e603e2a675a49a7f46ad3f49"
 dependencies = [
- "tokio",
+ "tokio 1.34.0",
 ]
 
 [[package]]
@@ -1056,7 +1536,7 @@ dependencies = [
  "convert_case",
  "proc-macro2",
  "quote",
- "rustc_version",
+ "rustc_version 0.4.0",
  "syn 1.0.109",
 ]
 
@@ -1064,9 +1544,18 @@ dependencies = [
 name = "des"
 version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e"
+checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e"
+dependencies = [
+ "cipher",
+]
+
+[[package]]
+name = "digest"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
 dependencies = [
- "cipher",
+ "generic-array",
 ]
 
 [[package]]
@@ -1075,7 +1564,7 @@ version = "0.10.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
 dependencies = [
- "block-buffer",
+ "block-buffer 0.10.4",
  "crypto-common",
  "subtle",
 ]
@@ -1097,9 +1586,15 @@ checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
 dependencies = [
  "libc",
  "redox_users",
- "winapi",
+ "winapi 0.3.9",
 ]
 
+[[package]]
+name = "discard"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
+
 [[package]]
 name = "dlv-list"
 version = "0.3.0"
@@ -1145,7 +1640,19 @@ version = "0.8.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "enum-as-inner"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "570d109b813e904becc80d8d5da38376818a143348413f7149f1340fe04754d4"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
 ]
 
 [[package]]
@@ -1164,6 +1671,15 @@ dependencies = [
  "windows-sys 0.48.0",
 ]
 
+[[package]]
+name = "error-chain"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc"
+dependencies = [
+ "version_check",
+]
+
 [[package]]
 name = "event-listener"
 version = "2.5.3"
@@ -1178,7 +1694,7 @@ checksum = "770d968249b5d99410d61f5bf89057f3199a077a04d087092f58e7d10692baae"
 dependencies = [
  "concurrent-queue",
  "parking",
- "pin-project-lite",
+ "pin-project-lite 0.2.13",
 ]
 
 [[package]]
@@ -1188,7 +1704,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3"
 dependencies = [
  "event-listener 4.0.0",
- "pin-project-lite",
+ "pin-project-lite 0.2.13",
 ]
 
 [[package]]
@@ -1221,6 +1737,12 @@ version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6"
 
+[[package]]
+name = "fixedbitset"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
+
 [[package]]
 name = "flate2"
 version = "1.0.28"
@@ -1239,7 +1761,7 @@ checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577"
 dependencies = [
  "futures-core",
  "futures-sink",
- "pin-project",
+ "pin-project 1.1.3",
  "spin 0.9.8",
 ]
 
@@ -1282,6 +1804,22 @@ dependencies = [
  "percent-encoding",
 ]
 
+[[package]]
+name = "fuchsia-zircon"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
+dependencies = [
+ "bitflags 1.3.2",
+ "fuchsia-zircon-sys",
+]
+
+[[package]]
+name = "fuchsia-zircon-sys"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
+
 [[package]]
 name = "futures"
 version = "0.3.29"
@@ -1352,7 +1890,7 @@ dependencies = [
  "futures-io",
  "memchr",
  "parking",
- "pin-project-lite",
+ "pin-project-lite 0.2.13",
  "waker-fn",
 ]
 
@@ -1367,7 +1905,7 @@ dependencies = [
  "futures-io",
  "memchr",
  "parking",
- "pin-project-lite",
+ "pin-project-lite 0.2.13",
 ]
 
 [[package]]
@@ -1412,11 +1950,20 @@ dependencies = [
  "futures-sink",
  "futures-task",
  "memchr",
- "pin-project-lite",
+ "pin-project-lite 0.2.13",
  "pin-utils",
  "slab",
 ]
 
+[[package]]
+name = "fxhash"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
+dependencies = [
+ "byteorder",
+]
+
 [[package]]
 name = "generic-array"
 version = "0.14.7"
@@ -1434,7 +1981,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e"
 dependencies = [
  "libc",
- "winapi",
+ "winapi 0.3.9",
 ]
 
 [[package]]
@@ -1443,7 +1990,7 @@ version = "0.1.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "libc",
  "wasi 0.9.0+wasi-snapshot-preview1",
 ]
@@ -1454,9 +2001,11 @@ version = "0.2.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
+ "js-sys",
  "libc",
  "wasi 0.11.0+wasi-snapshot-preview1",
+ "wasm-bindgen",
 ]
 
 [[package]]
@@ -1471,13 +2020,45 @@ version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
 
+[[package]]
+name = "gloo-timers"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "h2"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e4728fd124914ad25e99e3d15a9361a879f6620f63cb56bbb08f95abb97a535"
+dependencies = [
+ "bytes 0.5.6",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http",
+ "indexmap 1.9.3",
+ "slab",
+ "tokio 0.2.25",
+ "tokio-util 0.3.1",
+ "tracing",
+ "tracing-futures",
+]
+
 [[package]]
 name = "h2"
 version = "0.3.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178"
 dependencies = [
- "bytes",
+ "bytes 1.5.0",
  "fnv",
  "futures-core",
  "futures-sink",
@@ -1485,11 +2066,20 @@ dependencies = [
  "http",
  "indexmap 2.1.0",
  "slab",
- "tokio",
- "tokio-util",
+ "tokio 1.34.0",
+ "tokio-util 0.7.10",
  "tracing",
 ]
 
+[[package]]
+name = "hashbrown"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+dependencies = [
+ "ahash 0.7.7",
+]
+
 [[package]]
 name = "hashbrown"
 version = "0.12.3"
@@ -1554,7 +2144,18 @@ version = "0.12.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
 dependencies = [
- "digest",
+ "digest 0.10.7",
+]
+
+[[package]]
+name = "hostname"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
+dependencies = [
+ "libc",
+ "match_cfg",
+ "winapi 0.3.9",
 ]
 
 [[package]]
@@ -1563,9 +2164,9 @@ version = "0.2.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb"
 dependencies = [
- "bytes",
+ "bytes 1.5.0",
  "fnv",
- "itoa",
+ "itoa 1.0.9",
 ]
 
 [[package]]
@@ -1574,9 +2175,9 @@ version = "0.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
 dependencies = [
- "bytes",
+ "bytes 1.5.0",
  "http",
- "pin-project-lite",
+ "pin-project-lite 0.2.13",
 ]
 
 [[package]]
@@ -1591,7 +2192,7 @@ dependencies = [
  "futures-lite 1.13.0",
  "http",
  "infer",
- "pin-project-lite",
+ "pin-project-lite 0.2.13",
  "rand 0.7.3",
  "serde",
  "serde_json",
@@ -1618,19 +2219,19 @@ version = "0.14.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468"
 dependencies = [
- "bytes",
+ "bytes 1.5.0",
  "futures-channel",
  "futures-core",
  "futures-util",
- "h2",
+ "h2 0.3.22",
  "http",
  "http-body",
  "httparse",
  "httpdate",
- "itoa",
- "pin-project-lite",
+ "itoa 1.0.9",
+ "pin-project-lite 0.2.13",
  "socket2 0.4.10",
- "tokio",
+ "tokio 1.34.0",
  "tower-service",
  "tracing",
  "want",
@@ -1642,10 +2243,10 @@ version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
 dependencies = [
- "bytes",
+ "bytes 1.5.0",
  "hyper",
  "native-tls",
- "tokio",
+ "tokio 1.34.0",
  "tokio-native-tls",
 ]
 
@@ -1678,6 +2279,17 @@ version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
 
+[[package]]
+name = "idna"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
+dependencies = [
+ "matches",
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
 [[package]]
 name = "idna"
 version = "0.5.0"
@@ -1732,7 +2344,7 @@ version = "0.1.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
 ]
 
 [[package]]
@@ -1746,6 +2358,27 @@ dependencies = [
  "windows-sys 0.48.0",
 ]
 
+[[package]]
+name = "iovec"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "ipconfig"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7"
+dependencies = [
+ "socket2 0.3.19",
+ "widestring",
+ "winapi 0.3.9",
+ "winreg 0.6.2",
+]
+
 [[package]]
 name = "ipnet"
 version = "2.9.0"
@@ -1770,6 +2403,12 @@ dependencies = [
  "either",
 ]
 
+[[package]]
+name = "itoa"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
+
 [[package]]
 name = "itoa"
 version = "1.0.9"
@@ -1805,6 +2444,31 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "kernel32-sys"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
+dependencies = [
+ "winapi 0.2.8",
+ "winapi-build",
+]
+
+[[package]]
+name = "kv-log-macro"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f"
+dependencies = [
+ "log",
+]
+
+[[package]]
+name = "language-tags"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
+
 [[package]]
 name = "language-tags"
 version = "0.3.2"
@@ -1907,6 +2571,24 @@ name = "log"
 version = "0.4.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+dependencies = [
+ "value-bag",
+]
+
+[[package]]
+name = "lru-cache"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
+dependencies = [
+ "linked-hash-map",
+]
+
+[[package]]
+name = "match_cfg"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
 
 [[package]]
 name = "matchers"
@@ -1917,14 +2599,20 @@ dependencies = [
  "regex-automata 0.1.10",
 ]
 
+[[package]]
+name = "matches"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
+
 [[package]]
 name = "md-5"
 version = "0.10.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
 dependencies = [
- "cfg-if",
- "digest",
+ "cfg-if 1.0.0",
+ "digest 0.10.7",
 ]
 
 [[package]]
@@ -1939,6 +2627,21 @@ version = "0.3.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
 
+[[package]]
+name = "mini-moka"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c325dfab65f261f386debee8b0969da215b3fa0037e74c8a1234db7ba986d803"
+dependencies = [
+ "crossbeam-channel",
+ "crossbeam-utils",
+ "dashmap",
+ "skeptic",
+ "smallvec",
+ "tagptr",
+ "triomphe",
+]
+
 [[package]]
 name = "minimal-lexical"
 version = "0.2.1"
@@ -1954,6 +2657,25 @@ dependencies = [
  "adler",
 ]
 
+[[package]]
+name = "mio"
+version = "0.6.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4"
+dependencies = [
+ "cfg-if 0.1.10",
+ "fuchsia-zircon",
+ "fuchsia-zircon-sys",
+ "iovec",
+ "kernel32-sys",
+ "libc",
+ "log",
+ "miow",
+ "net2",
+ "slab",
+ "winapi 0.2.8",
+]
+
 [[package]]
 name = "mio"
 version = "0.8.9"
@@ -1966,6 +2688,29 @@ dependencies = [
  "windows-sys 0.48.0",
 ]
 
+[[package]]
+name = "mio-uds"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0"
+dependencies = [
+ "iovec",
+ "libc",
+ "mio 0.6.23",
+]
+
+[[package]]
+name = "miow"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d"
+dependencies = [
+ "kernel32-sys",
+ "net2",
+ "winapi 0.2.8",
+ "ws2_32-sys",
+]
+
 [[package]]
 name = "mutually_exclusive_features"
 version = "0.0.3"
@@ -1990,6 +2735,17 @@ dependencies = [
  "tempfile",
 ]
 
+[[package]]
+name = "net2"
+version = "0.2.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b13b648036a2339d06de780866fbdfda0dde886de7b3af2ddeba8b14f4ee34ac"
+dependencies = [
+ "cfg-if 0.1.10",
+ "libc",
+ "winapi 0.3.9",
+]
+
 [[package]]
 name = "nom"
 version = "7.1.3"
@@ -2007,7 +2763,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
 dependencies = [
  "overload",
- "winapi",
+ "winapi 0.3.9",
 ]
 
 [[package]]
@@ -2044,6 +2800,12 @@ version = "1.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
 
+[[package]]
+name = "opaque-debug"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
+
 [[package]]
 name = "openssl"
 version = "0.10.60"
@@ -2051,7 +2813,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800"
 dependencies = [
  "bitflags 2.4.1",
- "cfg-if",
+ "cfg-if 1.0.0",
  "foreign-types",
  "libc",
  "once_cell",
@@ -2117,7 +2879,7 @@ dependencies = [
  "hmac",
  "lazy_static",
  "rc2",
- "sha1",
+ "sha1 0.10.6",
  "yasna",
 ]
 
@@ -2154,12 +2916,12 @@ version = "0.8.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "instant",
  "libc",
  "redox_syscall 0.2.16",
  "smallvec",
- "winapi",
+ "winapi 0.3.9",
 ]
 
 [[package]]
@@ -2168,7 +2930,7 @@ version = "0.9.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "libc",
  "redox_syscall 0.4.1",
  "smallvec",
@@ -2238,13 +3000,43 @@ dependencies = [
  "sha2",
 ]
 
+[[package]]
+name = "petgraph"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
+dependencies = [
+ "fixedbitset",
+ "indexmap 2.1.0",
+]
+
+[[package]]
+name = "pin-project"
+version = "0.4.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ef0f924a5ee7ea9cbcea77529dba45f8a9ba9f622419fe3386ca581a3ae9d5a"
+dependencies = [
+ "pin-project-internal 0.4.30",
+]
+
 [[package]]
 name = "pin-project"
 version = "1.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422"
 dependencies = [
- "pin-project-internal",
+ "pin-project-internal 1.1.3",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "0.4.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "851c8d0ce9bebe43790dedfc86614c23494ac9f423dd618d3a61fc693eafe61e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
 ]
 
 [[package]]
@@ -2258,6 +3050,12 @@ dependencies = [
  "syn 2.0.39",
 ]
 
+[[package]]
+name = "pin-project-lite"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777"
+
 [[package]]
 name = "pin-project-lite"
 version = "0.2.13"
@@ -2307,11 +3105,11 @@ checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
 dependencies = [
  "autocfg",
  "bitflags 1.3.2",
- "cfg-if",
+ "cfg-if 1.0.0",
  "concurrent-queue",
  "libc",
  "log",
- "pin-project-lite",
+ "pin-project-lite 0.2.13",
  "windows-sys 0.48.0",
 ]
 
@@ -2321,9 +3119,9 @@ version = "3.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cf63fa624ab313c11656b4cda960bfc46c410187ad493c41f6ba2d8c1e991c9e"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "concurrent-queue",
- "pin-project-lite",
+ "pin-project-lite 0.2.13",
  "rustix 0.38.25",
  "tracing",
  "windows-sys 0.52.0",
@@ -2365,6 +3163,12 @@ dependencies = [
  "version_check",
 ]
 
+[[package]]
+name = "proc-macro-hack"
+version = "0.5.20+deprecated"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
+
 [[package]]
 name = "proc-macro2"
 version = "1.0.70"
@@ -2374,6 +3178,23 @@ dependencies = [
  "unicode-ident",
 ]
 
+[[package]]
+name = "pulldown-cmark"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998"
+dependencies = [
+ "bitflags 1.3.2",
+ "memchr",
+ "unicase",
+]
+
+[[package]]
+name = "quick-error"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
+
 [[package]]
 name = "quote"
 version = "1.0.33"
@@ -2554,11 +3375,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b"
 dependencies = [
  "base64 0.21.5",
- "bytes",
+ "bytes 1.5.0",
  "encoding_rs",
  "futures-core",
  "futures-util",
- "h2",
+ "h2 0.3.22",
  "http",
  "http-body",
  "hyper",
@@ -2570,19 +3391,29 @@ dependencies = [
  "native-tls",
  "once_cell",
  "percent-encoding",
- "pin-project-lite",
+ "pin-project-lite 0.2.13",
  "serde",
  "serde_json",
  "serde_urlencoded",
  "system-configuration",
- "tokio",
+ "tokio 1.34.0",
  "tokio-native-tls",
  "tower-service",
  "url",
  "wasm-bindgen",
  "wasm-bindgen-futures",
  "web-sys",
- "winreg",
+ "winreg 0.50.0",
+]
+
+[[package]]
+name = "resolv-conf"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00"
+dependencies = [
+ "hostname",
+ "quick-error",
 ]
 
 [[package]]
@@ -2591,6 +3422,34 @@ version = "0.1.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0"
 
+[[package]]
+name = "rhai"
+version = "1.16.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3625f343d89990133d013e39c46e350915178cf94f1bec9f49b0cbef98a3e3c"
+dependencies = [
+ "ahash 0.8.6",
+ "bitflags 2.4.1",
+ "instant",
+ "num-traits",
+ "once_cell",
+ "rhai_codegen",
+ "serde",
+ "smallvec",
+ "smartstring",
+]
+
+[[package]]
+name = "rhai_codegen"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "853977598f084a492323fe2f7896b4100a86284ee8473612de60021ea341310f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+]
+
 [[package]]
 name = "ring"
 version = "0.16.20"
@@ -2603,7 +3462,7 @@ dependencies = [
  "spin 0.5.2",
  "untrusted 0.7.1",
  "web-sys",
- "winapi",
+ "winapi 0.3.9",
 ]
 
 [[package]]
@@ -2620,6 +3479,16 @@ dependencies = [
  "windows-sys 0.48.0",
 ]
 
+[[package]]
+name = "ritelinked"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98f2771d255fd99f0294f13249fecd0cae6e074f86b4197ec1f1689d537b44d3"
+dependencies = [
+ "ahash 0.7.7",
+ "hashbrown 0.11.2",
+]
+
 [[package]]
 name = "ron"
 version = "0.7.1"
@@ -2637,7 +3506,7 @@ version = "0.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "ordered-multimap",
 ]
 
@@ -2647,13 +3516,22 @@ version = "0.1.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
 
+[[package]]
+name = "rustc_version"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
+dependencies = [
+ "semver 0.9.0",
+]
+
 [[package]]
 name = "rustc_version"
 version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
 dependencies = [
- "semver",
+ "semver 1.0.20",
 ]
 
 [[package]]
@@ -2756,6 +3634,15 @@ version = "1.0.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
 
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
 [[package]]
 name = "schannel"
 version = "0.1.22"
@@ -2804,11 +3691,29 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "semver"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
+dependencies = [
+ "semver-parser",
+]
+
 [[package]]
 name = "semver"
 version = "1.0.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "semver-parser"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
 
 [[package]]
 name = "serde"
@@ -2836,7 +3741,7 @@ version = "1.0.108"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
 dependencies = [
- "itoa",
+ "itoa 1.0.9",
  "ryu",
  "serde",
 ]
@@ -2847,7 +3752,7 @@ version = "0.1.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335"
 dependencies = [
- "itoa",
+ "itoa 1.0.9",
  "serde",
 ]
 
@@ -2869,7 +3774,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
 dependencies = [
  "form_urlencoded",
- "itoa",
+ "itoa 1.0.9",
  "ryu",
  "serde",
 ]
@@ -2925,32 +3830,60 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c"
 dependencies = [
  "indexmap 2.1.0",
- "itoa",
+ "itoa 1.0.9",
  "ryu",
  "serde",
  "unsafe-libyaml",
 ]
 
+[[package]]
+name = "sha-1"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6"
+dependencies = [
+ "block-buffer 0.9.0",
+ "cfg-if 1.0.0",
+ "cpufeatures",
+ "digest 0.9.0",
+ "opaque-debug",
+]
+
+[[package]]
+name = "sha1"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770"
+dependencies = [
+ "sha1_smol",
+]
+
 [[package]]
 name = "sha1"
 version = "0.10.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "cpufeatures",
- "digest",
+ "digest 0.10.7",
 ]
 
+[[package]]
+name = "sha1_smol"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
+
 [[package]]
 name = "sha2"
 version = "0.10.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "cpufeatures",
- "digest",
+ "digest 0.10.7",
 ]
 
 [[package]]
@@ -2971,6 +3904,21 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "skeptic"
+version = "0.13.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8"
+dependencies = [
+ "bytecount",
+ "cargo_metadata",
+ "error-chain",
+ "glob",
+ "pulldown-cmark",
+ "tempfile",
+ "walkdir",
+]
+
 [[package]]
 name = "slab"
 version = "0.4.9"
@@ -2985,6 +3933,32 @@ name = "smallvec"
 version = "1.11.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "smartstring"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
+dependencies = [
+ "autocfg",
+ "serde",
+ "static_assertions",
+ "version_check",
+]
+
+[[package]]
+name = "socket2"
+version = "0.3.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "winapi 0.3.9",
+]
 
 [[package]]
 name = "socket2"
@@ -2993,7 +3967,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
 dependencies = [
  "libc",
- "winapi",
+ "winapi 0.3.9",
 ]
 
 [[package]]
@@ -3053,7 +4027,7 @@ dependencies = [
  "base64 0.13.1",
  "bitflags 1.3.2",
  "byteorder",
- "bytes",
+ "bytes 1.5.0",
  "chrono",
  "crc",
  "crossbeam-queue",
@@ -3070,7 +4044,7 @@ dependencies = [
  "hkdf",
  "hmac",
  "indexmap 1.9.3",
- "itoa",
+ "itoa 1.0.9",
  "libc",
  "log",
  "md-5",
@@ -3083,7 +4057,7 @@ dependencies = [
  "rustls-pemfile",
  "serde",
  "serde_json",
- "sha1",
+ "sha1 0.10.6",
  "sha2",
  "smallvec",
  "sqlformat",
@@ -3126,7 +4100,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "804d3f245f894e61b1e6263c84b23ca675d96753b5abfd5cc8597d86806e8024"
 dependencies = [
  "once_cell",
- "tokio",
+ "tokio 1.34.0",
  "tokio-rustls",
 ]
 
@@ -3134,9 +4108,10 @@ dependencies = [
 name = "stacker"
 version = "0.1.0"
 dependencies = [
+ "actix-casbin-auth",
  "actix-cors",
- "actix-http",
- "actix-web",
+ "actix-http 3.4.0",
+ "actix-web 4.4.0",
  "actix-web-httpauth",
  "chrono",
  "clap",
@@ -3163,7 +4138,7 @@ dependencies = [
  "sha2",
  "sqlx",
  "thiserror",
- "tokio",
+ "tokio 1.34.0",
  "tokio-stream",
  "tracing",
  "tracing-actix-web",
@@ -3174,6 +4149,70 @@ dependencies = [
  "wiremock",
 ]
 
+[[package]]
+name = "standback"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff"
+dependencies = [
+ "version_check",
+]
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "stdweb"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5"
+dependencies = [
+ "discard",
+ "rustc_version 0.2.3",
+ "stdweb-derive",
+ "stdweb-internal-macros",
+ "stdweb-internal-runtime",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "stdweb-derive"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_derive",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "stdweb-internal-macros"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11"
+dependencies = [
+ "base-x",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "sha1 0.6.1",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "stdweb-internal-runtime"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
+
 [[package]]
 name = "stringprep"
 version = "0.1.4"
@@ -3240,13 +4279,19 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "tagptr"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417"
+
 [[package]]
 name = "tcp-stream"
 version = "0.26.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4da30af7998f51ee1aa48ab24276fe303a697b004e31ff542b192c088d5630a5"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "p12",
  "rustls-connector",
  "rustls-pemfile",
@@ -3258,7 +4303,7 @@ version = "3.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "fastrand 2.0.1",
  "redox_syscall 0.4.1",
  "rustix 0.38.25",
@@ -3291,10 +4336,19 @@ version = "1.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "once_cell",
 ]
 
+[[package]]
+name = "threadpool"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
+dependencies = [
+ "num_cpus",
+]
+
 [[package]]
 name = "time"
 version = "0.1.45"
@@ -3303,7 +4357,22 @@ checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
 dependencies = [
  "libc",
  "wasi 0.10.0+wasi-snapshot-preview1",
- "winapi",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "time"
+version = "0.2.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242"
+dependencies = [
+ "const_fn",
+ "libc",
+ "standback",
+ "stdweb",
+ "time-macros 0.1.1",
+ "version_check",
+ "winapi 0.3.9",
 ]
 
 [[package]]
@@ -3313,11 +4382,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
 dependencies = [
  "deranged",
- "itoa",
+ "itoa 1.0.9",
  "powerfmt",
  "serde",
  "time-core",
- "time-macros",
+ "time-macros 0.2.15",
 ]
 
 [[package]]
@@ -3326,6 +4395,16 @@ version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
 
+[[package]]
+name = "time-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1"
+dependencies = [
+ "proc-macro-hack",
+ "time-macros-impl",
+]
+
 [[package]]
 name = "time-macros"
 version = "0.2.15"
@@ -3335,6 +4414,28 @@ dependencies = [
  "time-core",
 ]
 
+[[package]]
+name = "time-macros-impl"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f"
+dependencies = [
+ "proc-macro-hack",
+ "proc-macro2",
+ "quote",
+ "standback",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "tiny-keccak"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
+dependencies = [
+ "crunchy",
+]
+
 [[package]]
 name = "tinyvec"
 version = "1.6.0"
@@ -3350,6 +4451,26 @@ version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
 
+[[package]]
+name = "tokio"
+version = "0.2.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092"
+dependencies = [
+ "bytes 0.5.6",
+ "futures-core",
+ "iovec",
+ "lazy_static",
+ "libc",
+ "memchr",
+ "mio 0.6.23",
+ "mio-uds",
+ "pin-project-lite 0.1.12",
+ "signal-hook-registry",
+ "slab",
+ "winapi 0.3.9",
+]
+
 [[package]]
 name = "tokio"
 version = "1.34.0"
@@ -3357,12 +4478,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9"
 dependencies = [
  "backtrace",
- "bytes",
+ "bytes 1.5.0",
  "libc",
- "mio",
+ "mio 0.8.9",
  "num_cpus",
  "parking_lot 0.12.1",
- "pin-project-lite",
+ "pin-project-lite 0.2.13",
  "signal-hook-registry",
  "socket2 0.5.5",
  "tokio-macros",
@@ -3377,7 +4498,7 @@ checksum = "802ccf58e108fe16561f35348fabe15ff38218968f033d587e399a84937533cc"
 dependencies = [
  "async-trait",
  "executor-trait",
- "tokio",
+ "tokio 1.34.0",
 ]
 
 [[package]]
@@ -3398,7 +4519,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
 dependencies = [
  "native-tls",
- "tokio",
+ "tokio 1.34.0",
 ]
 
 [[package]]
@@ -3408,7 +4529,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
 dependencies = [
  "rustls 0.20.9",
- "tokio",
+ "tokio 1.34.0",
  "webpki",
 ]
 
@@ -3419,8 +4540,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842"
 dependencies = [
  "futures-core",
- "pin-project-lite",
- "tokio",
+ "pin-project-lite 0.2.13",
+ "tokio 1.34.0",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499"
+dependencies = [
+ "bytes 0.5.6",
+ "futures-core",
+ "futures-sink",
+ "log",
+ "pin-project-lite 0.1.12",
+ "tokio 0.2.25",
 ]
 
 [[package]]
@@ -3429,11 +4564,11 @@ version = "0.7.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
 dependencies = [
- "bytes",
+ "bytes 1.5.0",
  "futures-core",
  "futures-sink",
- "pin-project-lite",
- "tokio",
+ "pin-project-lite 0.2.13",
+ "tokio 1.34.0",
  "tracing",
 ]
 
@@ -3459,7 +4594,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
 dependencies = [
  "log",
- "pin-project-lite",
+ "pin-project-lite 0.2.13",
  "tracing-attributes",
  "tracing-core",
 ]
@@ -3470,9 +4605,9 @@ version = "0.7.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1fe0d5feac3f4ca21ba33496bcb1ccab58cca6412b1405ae80f0581541e0ca78"
 dependencies = [
- "actix-web",
+ "actix-web 4.4.0",
  "mutually_exclusive_features",
- "pin-project",
+ "pin-project 1.1.3",
  "tracing",
  "uuid",
 ]
@@ -3516,6 +4651,16 @@ dependencies = [
  "valuable",
 ]
 
+[[package]]
+name = "tracing-futures"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2"
+dependencies = [
+ "pin-project 1.1.3",
+ "tracing",
+]
+
 [[package]]
 name = "tracing-log"
 version = "0.1.4"
@@ -3556,6 +4701,51 @@ dependencies = [
  "tracing-log 0.2.0",
 ]
 
+[[package]]
+name = "triomphe"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3"
+
+[[package]]
+name = "trust-dns-proto"
+version = "0.19.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cad71a0c0d68ab9941d2fb6e82f8fb2e86d9945b94e1661dd0aaea2b88215a9"
+dependencies = [
+ "async-trait",
+ "cfg-if 1.0.0",
+ "enum-as-inner",
+ "futures",
+ "idna 0.2.3",
+ "lazy_static",
+ "log",
+ "rand 0.7.3",
+ "smallvec",
+ "thiserror",
+ "tokio 0.2.25",
+ "url",
+]
+
+[[package]]
+name = "trust-dns-resolver"
+version = "0.19.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "710f593b371175db53a26d0b38ed2978fafb9e9e8d3868b1acd753ea18df0ceb"
+dependencies = [
+ "cfg-if 0.1.10",
+ "futures",
+ "ipconfig",
+ "lazy_static",
+ "log",
+ "lru-cache",
+ "resolv-conf",
+ "smallvec",
+ "thiserror",
+ "tokio 0.2.25",
+ "trust-dns-proto",
+]
+
 [[package]]
 name = "try-lock"
 version = "0.2.4"
@@ -3574,6 +4764,15 @@ version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
 
+[[package]]
+name = "unicase"
+version = "2.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
+dependencies = [
+ "version_check",
+]
+
 [[package]]
 name = "unicode-bidi"
 version = "0.3.13"
@@ -3632,7 +4831,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
 dependencies = [
  "form_urlencoded",
- "idna",
+ "idna 0.5.0",
  "percent-encoding",
  "serde",
 ]
@@ -3659,6 +4858,12 @@ version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
 
+[[package]]
+name = "value-bag"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cdbaf5e132e593e9fc1de6a15bbec912395b11fb9719e061cf64f804524c503"
+
 [[package]]
 name = "vcpkg"
 version = "0.2.15"
@@ -3677,6 +4882,16 @@ version = "1.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690"
 
+[[package]]
+name = "walkdir"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
 [[package]]
 name = "want"
 version = "0.3.1"
@@ -3710,7 +4925,7 @@ version = "0.2.88"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "wasm-bindgen-macro",
 ]
 
@@ -3735,7 +4950,7 @@ version = "0.4.38"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "js-sys",
  "wasm-bindgen",
  "web-sys",
@@ -3809,6 +5024,18 @@ dependencies = [
  "web-sys",
 ]
 
+[[package]]
+name = "widestring"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c"
+
+[[package]]
+name = "winapi"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
+
 [[package]]
 name = "winapi"
 version = "0.3.9"
@@ -3819,12 +5046,27 @@ dependencies = [
  "winapi-x86_64-pc-windows-gnu",
 ]
 
+[[package]]
+name = "winapi-build"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
+
 [[package]]
 name = "winapi-i686-pc-windows-gnu"
 version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
 
+[[package]]
+name = "winapi-util"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
+dependencies = [
+ "winapi 0.3.9",
+]
+
 [[package]]
 name = "winapi-x86_64-pc-windows-gnu"
 version = "0.4.0"
@@ -3972,13 +5214,22 @@ version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
 
+[[package]]
+name = "winreg"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9"
+dependencies = [
+ "winapi 0.3.9",
+]
+
 [[package]]
 name = "winreg"
 version = "0.50.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "windows-sys 0.48.0",
 ]
 
@@ -4001,7 +5252,17 @@ dependencies = [
  "regex",
  "serde",
  "serde_json",
- "tokio",
+ "tokio 1.34.0",
+]
+
+[[package]]
+name = "ws2_32-sys"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
+dependencies = [
+ "winapi 0.2.8",
+ "winapi-build",
 ]
 
 [[package]]
diff --git a/Cargo.toml b/Cargo.toml
index e13da269..024d61bf 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -55,6 +55,7 @@ clap = { version = "4.4.8", features = ["derive"] }
 serde_path_to_error = "0.1.14"
 deadpool-lapin = "0.11.0"
 docker-compose-types = "0.7.0"
+actix-casbin-auth = "0.4.4"
 
 [dependencies.sqlx]
 version = "0.6.3"
diff --git a/src/db/stack.rs b/src/db/stack.rs
index 92725cdb..def7a367 100644
--- a/src/db/stack.rs
+++ b/src/db/stack.rs
@@ -101,3 +101,40 @@ pub async fn insert(pool: &PgPool, mut stack: models::Stack) -> Result<models::S
         "Failed to insert".to_string()
     })
 }
+
+pub async fn update(pool: &PgPool, mut stack: models::Stack) -> Result<models::Stack, String> {
+    let query_span = tracing::info_span!("Updating user stack into the database");
+    sqlx::query_as!(
+        models::Stack,
+        r#"
+        UPDATE user_stack
+        SET 
+            stack_id=$2,
+            user_id=$3,
+            name=$4,
+            body=$5,
+            created_at=$6, 
+            updated_at=NOW() at time zone 'utc'
+        WHERE id = $1
+        RETURNING *
+        "#,
+        stack.id,
+        stack.stack_id,
+        stack.user_id,
+        stack.name,
+        stack.body,
+        stack.created_at,
+    )
+    .fetch_one(pool)
+    .instrument(query_span)
+    .await
+    .map(|result|{
+        tracing::info!("Stack {} have been saved to database", stack.id);
+        stack.updated_at = result.updated_at;
+        stack
+    })
+    .map_err(|err| {
+        tracing::error!("Failed to execute query: {:?}", err);
+        "".to_string()
+    })
+}
diff --git a/src/middleware/access_control.rs b/src/middleware/access_control.rs
new file mode 100644
index 00000000..80e48813
--- /dev/null
+++ b/src/middleware/access_control.rs
@@ -0,0 +1,5 @@
+pub struct AccessControl{}
+
+impl AccessControl {
+
+}
diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs
index 20dda301..66bc6dcf 100644
--- a/src/middleware/mod.rs
+++ b/src/middleware/mod.rs
@@ -1,2 +1,3 @@
 pub mod client;
 pub mod trydirect;
+pub mod access_control;
diff --git a/src/routes/stack/compose.rs b/src/routes/stack/compose.rs
index b8b51233..7c926b57 100644
--- a/src/routes/stack/compose.rs
+++ b/src/routes/stack/compose.rs
@@ -25,14 +25,12 @@ pub async fn add(
             None => Err(JsonResponse::<models::Stack>::build().not_found("not found")),
         })?;
 
-    let id = stack.id.clone();
-    let fc = DcBuilder::new(stack)
+    DcBuilder::new(stack)
         .build()
         .map_err(|err| {
             JsonResponse::<models::Stack>::build().internal_server_error(err)
-        })?;
-
-    Ok(JsonResponse::build().set_id(id).set_item(fc).ok("Success"))
+        })
+        .map(|fc| JsonResponse::build().set_id(id).set_item(fc).ok("Success"))
 }
 
 #[tracing::instrument(name = "Generate docker-compose. Admin")]
@@ -52,12 +50,10 @@ pub async fn admin(
             None => Err(JsonResponse::<models::Stack>::build().not_found("not found")),
         })?;
 
-    let id = stack.id.clone();
-    let fc = DcBuilder::new(stack)
+    DcBuilder::new(stack)
         .build()
         .map_err(|err| {
             JsonResponse::<models::Stack>::build().internal_server_error(err)
-        })?;
-
-    Ok(JsonResponse::build().set_id(id).set_item(fc).ok("Success"))
+        })
+        .map(|fc| JsonResponse::build().set_id(id).set_item(fc).ok("Success"))
 }
diff --git a/src/routes/stack/get.rs b/src/routes/stack/get.rs
index 1e9492a5..b261896a 100644
--- a/src/routes/stack/get.rs
+++ b/src/routes/stack/get.rs
@@ -17,18 +17,16 @@ pub async fn item(
     /// Get stack apps of logged user only
     let (id,) = path.into_inner();
 
-    let stack = db::stack::fetch(pg_pool.get_ref(), id)
+    db::stack::fetch(pg_pool.get_ref(), id)
         .await
         .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(err))
         .and_then(|stack| match stack {
             Some(stack) if stack.user_id != user.id => {
                 Err(JsonResponse::<models::Stack>::build().not_found("not found"))
             }
-            Some(stack) => Ok(stack),
+            Some(stack) => Ok(JsonResponse::build().set_item(Some(stack)).ok("OK")),
             None => Err(JsonResponse::<models::Stack>::build().not_found("not found")),
-        })?;
-
-    Ok(JsonResponse::build().set_item(Some(stack)).ok("OK"))
+        })
 }
 
 #[tracing::instrument(name = "Get user's stack list.")]
diff --git a/src/routes/stack/update.rs b/src/routes/stack/update.rs
index 316a1ba2..9768a6f6 100644
--- a/src/routes/stack/update.rs
+++ b/src/routes/stack/update.rs
@@ -1,10 +1,8 @@
 use crate::forms;
 use crate::helpers::JsonResponse;
 use crate::models;
-use crate::models::user::User;
-use actix_web::post;
-use actix_web::{web, web::Data, Responder, Result};
-use chrono::Utc;
+use crate::db;
+use actix_web::{web, web::Data, Responder, Result, post};
 use serde_json::Value;
 use serde_valid::Validate;
 use sqlx::PgPool;
@@ -17,92 +15,45 @@ use uuid::Uuid;
 pub async fn update(
     path: web::Path<(i32,)>,
     form: web::Json<forms::stack::Stack>,
-    user: web::ReqData<Arc<User>>,
+    user: web::ReqData<Arc<models::User>>,
     pg_pool: Data<PgPool>,
 ) -> Result<impl Responder> {
-    // @todo ACL
-
-    let (id,) = path.into_inner();
-    let query_span = tracing::info_span!("Check existence by id.");
-    match sqlx::query_as!(models::Stack, r"SELECT * FROM user_stack WHERE id = $1", id)
-        .fetch_one(pg_pool.get_ref())
-        .instrument(query_span)
+    let id = path.0;
+    let mut stack = db::stack::fetch(pg_pool.get_ref(), id)
         .await
-    {
-        Ok(stack) => {
-            tracing::info!("Found record: {:?}", stack.id);
-        }
-        Err(e) => {
-            tracing::error!("Failed to fetch record: {:?}, error: {:?}", id, e);
-            return Err(JsonResponse::<models::Stack>::build()
-                .not_found(format!("Object not found {}", id)));
-        }
-    };
+        .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(err))
+        .and_then(|stack| match stack {
+            Some(stack) => Ok(stack),
+            None => Err(JsonResponse::<models::Stack>::build().not_found("not found")),
+        })?;
 
     let stack_name = form.custom.custom_stack_code.clone();
     tracing::debug!("form data: {:?}", form);
     let user_id = user.id.clone();
-    let request_id = Uuid::new_v4();
-    let request_span = tracing::info_span!(
-        "Validating a stack", %request_id,
-        commonDomain=?&form.custom.project_name,
-        region=?&form.region,
-        domainList=?&form.domain_list
-    );
-    let _request_span_guard = request_span.enter(); // ->exit
-
-    tracing::info!(
-        "request_id {} Updating '{}' '{}'",
-        request_id,
-        form.custom.project_name,
-        form.region
-    );
-
-    let query_span = tracing::info_span!("Update stack details in db.");
 
-    if !form.validate().is_ok() {
-        let errors = form.validate().unwrap_err();
-        let err_msg = format!("Invalid data received {:?}", &errors.to_string());
-        tracing::debug!(err_msg);
+    if let Err(errors) = form.validate() {
         return Err(JsonResponse::<models::Stack>::build().bad_request(errors.to_string()));
     }
 
-    let body: Value = match serde_json::to_value::<forms::stack::Stack>(form.into_inner()) {
-        Ok(body) => body,
-        Err(err) => {
-            tracing::error!("Request_id {} error unwrap body {:?}", request_id, err);
-            serde_json::to_value::<forms::stack::Stack>(forms::stack::Stack::default()).unwrap()
-        }
-    };
+    let body: Value = serde_json::to_value::<forms::stack::Stack>(form.into_inner())
+        .map_err(|err| 
+            JsonResponse::<models::Stack>::build().bad_request(format!("{err}"))
+        )?; 
 
-    match sqlx::query!(
-        r#"
-        UPDATE user_stack
-        SET stack_id=$2, user_id=$3, name=$4, body=$5, created_at=$6, updated_at=$7
-        WHERE id=$1
-        "#,
-        id,
-        Uuid::new_v4(),
-        user_id,
-        stack_name,
-        body,
-        Utc::now(),
-        Utc::now(),
-    )
-    .execute(pg_pool.get_ref())
-    .instrument(query_span)
-    .await
-    {
-        Ok(_record) => {
-            tracing::info!(
-                "req_id: {} stack details have been saved to database",
-                request_id
-            );
-            return Ok(JsonResponse::<models::Stack>::build().set_id(id).ok("OK"));
-        }
-        Err(e) => {
-            tracing::error!("req_id: {} Failed to execute query: {:?}", request_id, e);
-            return Err(JsonResponse::<models::Stack>::build().bad_request("Internal Server Error"));
-        }
-    };
+    stack.stack_id = Uuid::new_v4();
+    stack.user_id = user_id;
+    stack.name = stack_name;
+    stack.body = body;
+
+    db::stack::update(pg_pool.get_ref(), stack)
+        .await
+        .map(|stack| {
+            JsonResponse::<models::Stack>::build()
+                .set_item(stack)
+                .ok("success")
+        })
+        .map_err(|err| {
+            tracing::error!("Failed to execute query: {:?}", err);
+            JsonResponse::<models::Stack>::build().internal_server_error("")
+        })
 }

From 05c9cc2f49cddb0f6c20b56fd052945af4f8d2fc Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Sun, 21 Jan 2024 11:57:02 +0200
Subject: [PATCH 147/284] dockerhub functionality refactoring

---
 src/forms/stack.rs              |  17 +-----
 src/helpers/dockerhub.rs        | 102 +++++++++++++++-----------------
 tests/custom-stack-payload.json |   1 -
 3 files changed, 50 insertions(+), 70 deletions(-)
 delete mode 100644 tests/custom-stack-payload.json

diff --git a/src/forms/stack.rs b/src/forms/stack.rs
index 99c4d6bc..4ee56b24 100644
--- a/src/forms/stack.rs
+++ b/src/forms/stack.rs
@@ -75,22 +75,9 @@ impl fmt::Display for DockerImage {
 
 impl DockerImage {
 
+    #[tracing::instrument(name="is_active")]
     async fn is_active(&self) -> Result<bool, String> {
-        tracing::debug!("Validate image at DockerHub {:?}", &self);
-        let mut dockerhub = DockerHub::build();
-
-        dockerhub
-            .login(
-                self.dockerhub_user.clone().unwrap_or("".to_string()).as_ref(),
-                self.dockerhub_password.clone().unwrap().as_ref(),
-            )
-            .await
-            .set_image(self.dockerhub_image.clone().unwrap_or(String::from("")).as_str())
-            .await
-            .set_repos(self.dockerhub_name.clone().unwrap_or(String::from("")).as_str())
-            .await
-            .is_active()
-            .await
+        DockerHub::from(self).is_active().await
     }
 }
 
diff --git a/src/helpers/dockerhub.rs b/src/helpers/dockerhub.rs
index 70a5e7a5..5074cb23 100644
--- a/src/helpers/dockerhub.rs
+++ b/src/helpers/dockerhub.rs
@@ -1,6 +1,8 @@
+use reqwest::RequestBuilder;
 use serde_derive::{Deserialize, Serialize};
 use serde_valid::Validate;
 use serde_json::Value;
+use crate::forms::stack::DockerImage;
 
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
@@ -61,79 +63,35 @@ struct TagResult {
 pub(crate) struct DockerHub<'a> {
     pub(crate) creds: DockerHubCreds<'a>,
     pub(crate) repos: String,
-    pub(crate) token: DockerHubToken,
     pub(crate) image: String,
 }
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Validate)]
-pub(crate) struct DockerHubBuilder<'a> {
-    pub(crate) creds: DockerHubCreds<'a>,
-    pub(crate) repos: &'a str,
-    pub(crate) token: DockerHubToken,
-    pub(crate) image: &'a str,
-}
 
-impl<'a> DockerHubBuilder<'a> {
-
-    pub async fn set_repos(&'a mut self, repo_name: &'a str) -> &'a mut Self {
-        self.repos = repo_name;
-        self
-    }
+impl<'a> DockerHub<'a> {
 
-    pub async fn set_image(&'a mut self, image: &'a str) -> &'a mut Self {
-        self.image = image;
-        self
-    }
-    pub async fn login(&'a mut self, username: &'a str, password: &'a str) -> &'a mut Self {
+    pub async fn login(&'a self) -> Result<DockerHubToken, String> {
         let endpoint = "https://hub.docker.com/v2/users/login";
 
-        self.creds = DockerHubCreds {
-            username: username,
-            password: password
-        };
-
-        if self.creds.password.is_empty() {
-            tracing::debug!("DockerHub credentials were not provided, login not required/image considered public");
-            return self;
-        }
-
-        let response = reqwest::Client::new()
+        reqwest::Client::new()
             .post(endpoint)
             .json(&self.creds)
             .send()
             .await
-            .map_err(|err| format!("{:?}", err))
-            .unwrap()
+            .map_err(|err| format!("{:?}", err))?
             .json::<DockerHubToken>()
             .await
             .map_err(|err| format!("{:?}", err))
-            .map(|token| {
-                self.token = token;
-            });
-
-        tracing::debug!("Login response was: {:?}", response);
-        self
     }
 
     pub async fn is_active(&'a self) -> Result<bool, String> {
+
         // get repo images
         let tags_url = format!("https://hub.docker.com/v2/namespaces/{}/repositories/{}/tags",
                                &self.creds.username, &self.repos);
-
         let mut client = reqwest::Client::new()
             .get(tags_url)
             .header("Accept", "application/json");
 
-        client = match self.token.token.as_ref() {
-            Some(token) => {
-                if !token.is_empty() {
-                    client = client.bearer_auth(token);
-                }
-                client
-            },
-            None => {
-                client
-            }
-        };
+        let mut client = self.set_token(client).await?;
 
         client
             .send()
@@ -155,11 +113,47 @@ impl<'a> DockerHubBuilder<'a> {
             })
     }
 
+    pub async fn set_token(&self, mut client: RequestBuilder) -> Result<RequestBuilder, String> {
+
+        if self.creds.password.is_empty() {
+            return Ok(client);
+        }
+        let token = self.login().await?;
+
+        match token.token {
+            None => {
+                Ok(client)
+            },
+            Some(token) => {
+                Ok(client.bearer_auth(token))
+            }
+        }
+    }
 }
 
-impl<'a> DockerHub<'a>
-{
-    pub fn build() -> DockerHubBuilder<'a> {
-        DockerHubBuilder::default()
+impl<'a> From <&'a DockerImage> for DockerHub<'a> {
+
+    fn from(image: &'a DockerImage) -> Self {
+        let username = match image.dockerhub_user {
+            Some(ref username) => {
+                username
+            },
+            None => ""
+        };
+        let password = match image.dockerhub_password {
+            Some(ref password) => {
+                password
+            },
+            None => ""
+        };
+
+        DockerHub {
+            creds: DockerHubCreds {
+                username: username,
+                password: password
+            },
+            repos: image.dockerhub_name.clone().unwrap_or("".to_string()),
+            image: format!("{}", image),
+        }
     }
 }
\ No newline at end of file
diff --git a/tests/custom-stack-payload.json b/tests/custom-stack-payload.json
deleted file mode 100644
index e85b469c..00000000
--- a/tests/custom-stack-payload.json
+++ /dev/null
@@ -1 +0,0 @@
-{"commonDomain":"","domainList":{},"region":"fsn1","zone":null,"server":"cx11","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":false,"disk_type":"pd-standart","cloud_token":"****************************","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"networks":["default_network"],"web":[{"name":"MyApp","code":"myapp","domain":"myapp.com","shared_ports":[{"host_port":"80","container_port":"8080"}],"versions":[],"custom":true,"type":"web","network":["default_network"],"restart":"always","timestamp":"2024-01-15T09:47:46.580Z","_id":"lreqrvv81ifrmng39","dockerhub_user":"trydirect","dockerhub_name":"nginx-waf", "dockerhub_password":  "", "environment":[{"key":"SOMEENVVAR1","value":"SOMEENVVAR_VALUE1"},{"key":"SOMEENVVAR2","value":"SOMEENVVAR_VALUE2"}],"volumes":[]}],"feature":[],"service":[],"servers_count":3,"project_git_url":"info@optimum-web.com","project_name":"Sample2024","custom_stack_code":"sample2024"}}
\ No newline at end of file

From 6fa70cffd6879bcab325bae5ea409fad2c0c0edf Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Mon, 22 Jan 2024 21:54:52 +0200
Subject: [PATCH 148/284] validate public/private repos

---
 src/forms/stack.rs       | 24 ++++-------
 src/helpers/dockerhub.rs | 89 ++++++++++++++++++++++++++++++++++++----
 2 files changed, 89 insertions(+), 24 deletions(-)

diff --git a/src/forms/stack.rs b/src/forms/stack.rs
index 4ee56b24..09512d4c 100644
--- a/src/forms/stack.rs
+++ b/src/forms/stack.rs
@@ -40,17 +40,21 @@ pub struct Port {
     pub protocol: Option<String>
 }
 
+
+
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct DockerImage  {
-    #[validate(min_length = 3)]
+    // #[validate(min_length = 3)]
     #[validate(max_length = 50)]
-    #[validate(pattern = r"^[a-z0-9]+([-_.][a-z0-9]+)*$")]
+    // @todo conditional check, if not empty
+    // #[validate(pattern = r"^[a-z0-9]+([-_.][a-z0-9]+)*$")]
     pub dockerhub_user: Option<String>,
-    #[validate(min_length = 3)]
+    // #[validate(min_length = 3)]
     #[validate(max_length = 50)]
-    #[validate(pattern = r"^[a-z0-9]+([-_.][a-z0-9]+)*$")]
+    // @todo conditional check, if not empty
+    // #[validate(pattern = r"^[a-z0-9]+([-_.][a-z0-9]+)*$")]
     pub dockerhub_name: Option<String>,
-    #[validate(min_length = 3)]
+    // #[validate(min_length = 3)]
     #[validate(max_length = 100)]
     pub dockerhub_image: Option<String>,
     pub dockerhub_password: Option<String>,
@@ -146,16 +150,6 @@ impl StackForm {
            }
         }
 
-        // self.custom.service
-        //     .into_iter()
-        //     .map(|item| {
-        //         item.iter()
-        //             .any(|item| {
-        //                 item.app.docker_image.is_active()?
-        //             })
-        //             .collect()
-        //     }).collect();
-        //
         if let Some(service) = &self.custom.service {
             for app in service {
                 if !app.app.docker_image.is_active().await? {
diff --git a/src/helpers/dockerhub.rs b/src/helpers/dockerhub.rs
index 5074cb23..e93783a2 100644
--- a/src/helpers/dockerhub.rs
+++ b/src/helpers/dockerhub.rs
@@ -58,6 +58,31 @@ struct TagResult {
     results: Vec<Tag>
 }
 
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct RepoResults {
+    pub count: i64,
+    pub next: Value,
+    pub previous: Value,
+    pub results: Vec<RepoResult>,
+}
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct RepoResult {
+    pub name: String,
+    pub namespace: String,
+    pub repository_type: String,
+    pub status: i64,
+    pub status_description: String,
+    pub description: String,
+    pub is_private: bool,
+    pub star_count: i64,
+    pub pull_count: i64,
+    pub last_updated: String,
+    pub date_registered: String,
+    pub affiliation: String,
+    pub media_types: Vec<String>,
+    pub content_types: Vec<String>,
+}
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Validate)]
 pub(crate) struct DockerHub<'a> {
@@ -82,25 +107,58 @@ impl<'a> DockerHub<'a> {
             .map_err(|err| format!("{:?}", err))
     }
 
-    pub async fn is_active(&'a self) -> Result<bool, String> {
-
-        // get repo images
-        let tags_url = format!("https://hub.docker.com/v2/namespaces/{}/repositories/{}/tags",
-                               &self.creds.username, &self.repos);
-        let mut client = reqwest::Client::new()
-            .get(tags_url)
+    pub async fn lookup_public_repo(&self) -> Result<bool, String> {
+        let url = format!("https://hub.docker.com/v2/repositories/{}", &self.repos);
+        tracing::debug!("Validate public repositories {:?}", url);
+        let client = reqwest::Client::new()
+            .get(url)
             .header("Accept", "application/json");
-
         let mut client = self.set_token(client).await?;
+        client
+            .send()
+            .await
+            .map_err(|err| {
+                tracing::debug!("Error response {:?}", err);
+                format!("{}", err)
+            })?
+            .json::<RepoResults>()
+            .await
+            .map_err(|err| format!("{}", err))
+            .map(|repositories| {
+                tracing::debug!("Get public image repositories response {:?}", repositories);
+                if repositories.count > 0 {
+                    // let's find at least one active tag
+                    let active = repositories.results
+                        .into_iter()
+                        .any(|mut repo| repo.status == 1 );
+                    active
+                } else {
+                    false
+                }
+            })
+    }
 
+    pub async fn lookup_private_repo(&self) -> Result<bool, String> {
+        let url = format!("https://hub.docker.com/v2/namespaces/{}/repositories/{}/tags",
+                          &self.creds.username,
+                          &self.repos);
+        tracing::debug!("Validate image {:?}", url);
+        let client = reqwest::Client::new()
+            .get(url)
+            .header("Accept", "application/json");
+        let mut client = self.set_token(client).await?;
         client
             .send()
             .await
             .map_err(|err| format!("{}", err))?
             .json::<TagResult>()
             .await
-            .map_err(|err| format!("{}", err))
+            .map_err(|err| {
+                tracing::debug!("Error response {:?}", err);
+                format!("{}", err)
+            })
             .map(|tags| {
+                tracing::debug!("Validate private image response {:?}", tags);
                 if tags.count > 0 {
                     // let's find at least one active tag
                     let active = tags.results
@@ -112,12 +170,25 @@ impl<'a> DockerHub<'a> {
                 }
             })
     }
+    pub async fn is_active(&'a self) -> Result<bool, String> {
+
+        // if namespace/user is not set change endpoint and return a different response
+        if self.creds.username.is_empty() {
+            return self.lookup_public_repo().await;
+        }
+        return self.lookup_private_repo().await;
+
+    }
 
     pub async fn set_token(&self, mut client: RequestBuilder) -> Result<RequestBuilder, String> {
 
         if self.creds.password.is_empty() {
+            tracing::debug!("Password is empty. Image should be public");
             return Ok(client);
+        } else{
+
         }
+        tracing::debug!("Password is set. Login..");
         let token = self.login().await?;
 
         match token.token {

From b954bd6f7489016db7e17bcee8a1b7c719fd2ed0 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Mon, 22 Jan 2024 22:47:52 +0200
Subject: [PATCH 149/284] install ca-certificates, fix external https call

---
 Dockerfile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Dockerfile b/Dockerfile
index 29b6b730..c068fc29 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -44,7 +44,7 @@ RUN apt-get update && apt-get install --no-install-recommends -y libssl-dev; \
 # deploy production
 FROM debian:bookworm-slim as production
 
-RUN apt-get update && apt-get install --no-install-recommends -y libssl-dev;
+RUN apt-get update && apt-get install --no-install-recommends -y libssl-dev ca-certificates;
 # create app directory
 WORKDIR /app
 RUN mkdir ./files && chmod 0777 ./files

From 745e57b8460a517ffb3245fb38caeb5031a3462d Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Tue, 23 Jan 2024 15:53:39 +0200
Subject: [PATCH 150/284] host port conditional validation, variant 1

---
 src/forms/stack.rs           | 61 +++++++++++++++++++++++++-----------
 src/helpers/stack/builder.rs | 28 +++++++++++++----
 2 files changed, 64 insertions(+), 25 deletions(-)

diff --git a/src/forms/stack.rs b/src/forms/stack.rs
index 09512d4c..07674fa3 100644
--- a/src/forms/stack.rs
+++ b/src/forms/stack.rs
@@ -2,10 +2,8 @@ use serde::{Deserialize, Serialize};
 use serde_json::Value;
 use serde_valid::Validate;
 use std::fmt;
-use serde_valid::validation::error::Format::Default;
+use regex::Regex;
 use crate::helpers::dockerhub::{DockerHub, DockerHubCreds, DockerHubToken};
-// use tokio::runtime::Runtime;
-// use tokio::runtime::Handle;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Role {
@@ -27,23 +25,48 @@ pub struct Requirements {
     #[validate(min_length = 1)]
     #[validate(max_length = 10)]
     #[validate(pattern = r"^\d+G$")]
-    pub ram_size: Option<String>
+    pub ram_size: Option<String>,
+}
+
+fn validate_non_empty(v: &Option<String>) -> Result<(), serde_valid::validation::Error> {
+    if v.is_none() {
+        return Ok(());
+    }
+
+    if let Some(value) = v {
+        if value.is_empty() {
+            return Ok(());
+        }
+
+        // #[validate(pattern = r"^\d{2,6}+$")]
+        let re = Regex::new(r"^\d{2,6}+$").unwrap();
+
+        if !re.is_match(value.as_str()) {
+            return Err(serde_valid::validation::Error::Custom("Port is not valid.".to_owned()));
+        }
+    }
+
+    Ok(())
+}
+
+#[derive(Validate)]
+struct Data {
+    val: i32,
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Port {
-    #[validate(pattern = r"^\d{2,6}+$")]
+    #[validate(custom(|v| validate_non_empty(v)))]
     pub host_port: Option<String>,
     #[validate(pattern = r"^\d{2,6}+$")]
     pub container_port: String,
     #[validate(enumerate("tcp", "udp"))]
-    pub protocol: Option<String>
+    pub protocol: Option<String>,
 }
 
 
-
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct DockerImage  {
+pub struct DockerImage {
     // #[validate(min_length = 3)]
     #[validate(max_length = 50)]
     // @todo conditional check, if not empty
@@ -78,8 +101,7 @@ impl fmt::Display for DockerImage {
 
 
 impl DockerImage {
-
-    #[tracing::instrument(name="is_active")]
+    #[tracing::instrument(name = "is_active")]
     async fn is_active(&self) -> Result<bool, String> {
         DockerHub::from(self).is_active().await
     }
@@ -94,7 +116,7 @@ impl AsRef<DockerImage> for App {
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct StackForm {
-    #[validate(max_length=255)]
+    #[validate(max_length = 255)]
     #[serde(rename = "commonDomain")]
     pub common_domain: Option<String>,
     pub domain_list: Option<DomainList>,
@@ -139,15 +161,13 @@ pub struct StackForm {
 }
 
 impl StackForm {
-
     pub async fn is_readable_docker_image(&self) -> Result<bool, String> {
-
         let mut is_active = true;
         for app in &self.custom.web {
-           if !app.app.docker_image.is_active().await? {
-               is_active = false;
-               break;
-           }
+            if !app.app.docker_image.is_active().await? {
+                is_active = false;
+                break;
+            }
         }
 
         if let Some(service) = &self.custom.service {
@@ -170,6 +190,7 @@ impl StackForm {
         Ok(is_active)
     }
 }
+
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 #[serde(rename_all = "snake_case")]
 pub struct StackPayload {
@@ -201,7 +222,7 @@ pub struct StackPayload {
     #[serde(rename = "selected_plan")]
     pub selected_plan: String,
     pub custom: Custom,
-    pub docker_compose: Option<Vec<u8>>
+    pub docker_compose: Option<Vec<u8>>,
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
@@ -216,6 +237,7 @@ pub struct Var {}
 pub struct Price {
     pub value: f64,
 }
+
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Custom {
     #[validate]
@@ -318,13 +340,14 @@ pub struct App {
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct EnvVar {
     pub(crate) key: String,
-    pub(crate) value: String
+    pub(crate) value: String,
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct Environment {
     pub(crate) environment: Option<Vec<EnvVar>>,
 }
+
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct Volume {
     pub(crate) host_path: Option<String>,
diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index 7652eb70..501d22ed 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -57,18 +57,34 @@ impl TryInto<AdvancedVolumes> for Volume {
 impl TryInto<Port> for stack::Port {
     type Error = String;
     fn try_into(self) -> Result<Port, Self::Error> {
-        let cp  = self.container_port.clone()
-            .parse::<u16>().map_err(|err| "Could not parse container port".to_string() )?;
-        let hp = self.host_port.clone()
-            .unwrap_or("".to_string())
-            .parse::<u16>().map_err(|err| "Could not parse host port".to_string() )?;
+        let cp = self.container_port
+            .clone()
+            .parse::<u16>()
+            .map_err(|err| "Could not parse container port".to_string() )?;
+
+        let hp = match self.host_port.clone() {
+            Some(hp) => {
+                if hp.is_empty() {
+                    None
+                } else {
+                    match hp.parse::<u16>() {
+                        Ok(port) => Some(PublishedPort::Single(port)),
+                        Err(_) => {
+                            tracing::debug!("Could not parse host port: {}", hp);
+                            None
+                        }
+                    }
+                }
+            }
+            _ => None
+        };
 
         tracing::debug!("Port conversion result: cp: {:?} hp: {:?}", cp, hp);
 
         Ok(Port {
             target: cp,
             host_ip: None,
-            published: Some(PublishedPort::Single(hp)),
+            published: hp,
             protocol: None,
             mode: None,
         })

From 1ed72dbe095faf8e531dea1590dae08d4bc6f711 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Tue, 23 Jan 2024 18:35:10 +0200
Subject: [PATCH 151/284] issue-auth working on casbin

---
 Cargo.lock                        | 1265 ++++-------------------------
 Cargo.toml                        |    2 +-
 rbac/rbac_with_pattern_model.conf |   15 +
 rbac/rbac_with_pattern_policy.csv |   10 +
 src/middleware/trydirect.rs       |    7 +
 src/routes/test/casbin.rs         |    8 +
 src/routes/test/deploy.rs         |    7 -
 src/routes/test/mod.rs            |    1 +
 src/startup.rs                    |   31 +
 9 files changed, 234 insertions(+), 1112 deletions(-)
 create mode 100644 rbac/rbac_with_pattern_model.conf
 create mode 100644 rbac/rbac_with_pattern_policy.csv
 create mode 100644 src/routes/test/casbin.rs

diff --git a/Cargo.lock b/Cargo.lock
index 39301be6..35a44030 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5,30 +5,13 @@ version = 3
 [[package]]
 name = "actix-casbin-auth"
 version = "0.4.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "feb9c094ecc5e0a561a44aee0c0de87be31c524b26f5096a8fde5c8de2eacec3"
+source = "git+https://github.com/casbin-rs/actix-casbin-auth.git#5f8550953a29460d18029fb50785773d6957d717"
 dependencies = [
- "actix-service 1.0.6",
- "actix-web 3.3.3",
- "async-std",
+ "actix-service",
+ "actix-web",
  "casbin",
  "futures",
-]
-
-[[package]]
-name = "actix-codec"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "78d1833b3838dbe990df0f1f87baf640cf6146e898166afe401839d1b001e570"
-dependencies = [
- "bitflags 1.3.2",
- "bytes 0.5.6",
- "futures-core",
- "futures-sink",
- "log",
- "pin-project 0.4.30",
- "tokio 0.2.25",
- "tokio-util 0.3.1",
+ "tokio",
 ]
 
 [[package]]
@@ -38,43 +21,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "617a8268e3537fe1d8c9ead925fca49ef6400927ee7bc26750e90ecee14ce4b8"
 dependencies = [
  "bitflags 1.3.2",
- "bytes 1.5.0",
+ "bytes",
  "futures-core",
  "futures-sink",
  "memchr",
- "pin-project-lite 0.2.13",
- "tokio 1.34.0",
- "tokio-util 0.7.10",
+ "pin-project-lite",
+ "tokio",
+ "tokio-util",
  "tracing",
 ]
 
-[[package]]
-name = "actix-connect"
-version = "2.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "177837a10863f15ba8d3ae3ec12fac1099099529ed20083a27fdfe247381d0dc"
-dependencies = [
- "actix-codec 0.3.0",
- "actix-rt 1.1.1",
- "actix-service 1.0.6",
- "actix-utils 2.0.0",
- "derive_more",
- "either",
- "futures-util",
- "http",
- "log",
- "trust-dns-proto",
- "trust-dns-resolver",
-]
-
 [[package]]
 name = "actix-cors"
 version = "0.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b340e9cfa5b08690aae90fb61beb44e9b06f44fe3d0f93781aaa58cfba86245e"
 dependencies = [
- "actix-utils 3.0.1",
- "actix-web 4.4.0",
+ "actix-utils",
+ "actix-web",
  "derive_more",
  "futures-util",
  "log",
@@ -82,100 +46,45 @@ dependencies = [
  "smallvec",
 ]
 
-[[package]]
-name = "actix-http"
-version = "2.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2be6b66b62a794a8e6d366ac9415bb7d475ffd1e9f4671f38c1d8a8a5df950b3"
-dependencies = [
- "actix-codec 0.3.0",
- "actix-connect",
- "actix-rt 1.1.1",
- "actix-service 1.0.6",
- "actix-threadpool",
- "actix-utils 2.0.0",
- "base64 0.13.1",
- "bitflags 1.3.2",
- "bytes 0.5.6",
- "cookie 0.14.4",
- "copyless",
- "derive_more",
- "either",
- "encoding_rs",
- "futures-channel",
- "futures-core",
- "futures-util",
- "fxhash",
- "h2 0.2.7",
- "http",
- "httparse",
- "indexmap 1.9.3",
- "itoa 0.4.8",
- "language-tags 0.2.2",
- "lazy_static",
- "log",
- "mime",
- "percent-encoding",
- "pin-project 1.1.3",
- "rand 0.7.3",
- "regex",
- "serde",
- "serde_json",
- "serde_urlencoded",
- "sha-1",
- "slab",
- "time 0.2.27",
-]
-
 [[package]]
 name = "actix-http"
 version = "3.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a92ef85799cba03f76e4f7c10f533e66d87c9a7e7055f3391f09000ad8351bc9"
 dependencies = [
- "actix-codec 0.5.1",
- "actix-rt 2.9.0",
- "actix-service 2.0.2",
- "actix-utils 3.0.1",
+ "actix-codec",
+ "actix-rt",
+ "actix-service",
+ "actix-utils",
  "ahash 0.8.6",
  "base64 0.21.5",
  "bitflags 2.4.1",
  "brotli",
- "bytes 1.5.0",
+ "bytes",
  "bytestring",
  "derive_more",
  "encoding_rs",
  "flate2",
  "futures-core",
- "h2 0.3.22",
+ "h2",
  "http",
  "httparse",
  "httpdate",
- "itoa 1.0.9",
- "language-tags 0.3.2",
+ "itoa",
+ "language-tags",
  "local-channel",
  "mime",
  "percent-encoding",
- "pin-project-lite 0.2.13",
+ "pin-project-lite",
  "rand 0.8.5",
- "sha1 0.10.6",
+ "sha1",
  "smallvec",
- "tokio 1.34.0",
- "tokio-util 0.7.10",
+ "tokio",
+ "tokio-util",
  "tracing",
  "zstd",
 ]
 
-[[package]]
-name = "actix-macros"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4ca8ce00b267af8ccebbd647de0d61e0674b6e61185cc7a592ff88772bed655"
-dependencies = [
- "quote",
- "syn 1.0.109",
-]
-
 [[package]]
 name = "actix-macros"
 version = "0.2.4"
@@ -186,19 +95,6 @@ dependencies = [
  "syn 2.0.39",
 ]
 
-[[package]]
-name = "actix-router"
-version = "0.2.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ad299af73649e1fc893e333ccf86f377751eb95ff875d095131574c6f43452c"
-dependencies = [
- "bytestring",
- "http",
- "log",
- "regex",
- "serde",
-]
-
 [[package]]
 name = "actix-router"
 version = "0.5.1"
@@ -212,21 +108,6 @@ dependencies = [
  "tracing",
 ]
 
-[[package]]
-name = "actix-rt"
-version = "1.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "143fcc2912e0d1de2bcf4e2f720d2a60c28652ab4179685a1ee159e0fb3db227"
-dependencies = [
- "actix-macros 0.1.3",
- "actix-threadpool",
- "copyless",
- "futures-channel",
- "futures-util",
- "smallvec",
- "tokio 0.2.25",
-]
-
 [[package]]
 name = "actix-rt"
 version = "2.9.0"
@@ -234,27 +115,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "28f32d40287d3f402ae0028a9d54bef51af15c8769492826a69d28f81893151d"
 dependencies = [
  "futures-core",
- "tokio 1.34.0",
-]
-
-[[package]]
-name = "actix-server"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "45407e6e672ca24784baa667c5d32ef109ccdd8d5e0b5ebb9ef8a67f4dfb708e"
-dependencies = [
- "actix-codec 0.3.0",
- "actix-rt 1.1.1",
- "actix-service 1.0.6",
- "actix-utils 2.0.0",
- "futures-channel",
- "futures-util",
- "log",
- "mio 0.6.23",
- "mio-uds",
- "num_cpus",
- "slab",
- "socket2 0.3.19",
+ "tokio",
 ]
 
 [[package]]
@@ -263,27 +124,17 @@ version = "2.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3eb13e7eef0423ea6eab0e59f6c72e7cb46d33691ad56a726b3cd07ddec2c2d4"
 dependencies = [
- "actix-rt 2.9.0",
- "actix-service 2.0.2",
- "actix-utils 3.0.1",
+ "actix-rt",
+ "actix-service",
+ "actix-utils",
  "futures-core",
  "futures-util",
- "mio 0.8.9",
+ "mio",
  "socket2 0.5.5",
- "tokio 1.34.0",
+ "tokio",
  "tracing",
 ]
 
-[[package]]
-name = "actix-service"
-version = "1.0.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0052435d581b5be835d11f4eb3bce417c8af18d87ddf8ace99f8e67e595882bb"
-dependencies = [
- "futures-util",
- "pin-project 0.4.30",
-]
-
 [[package]]
 name = "actix-service"
 version = "2.0.2"
@@ -292,68 +143,7 @@ checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a"
 dependencies = [
  "futures-core",
  "paste",
- "pin-project-lite 0.2.13",
-]
-
-[[package]]
-name = "actix-testing"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "47239ca38799ab74ee6a8a94d1ce857014b2ac36f242f70f3f75a66f691e791c"
-dependencies = [
- "actix-macros 0.1.3",
- "actix-rt 1.1.1",
- "actix-server 1.0.4",
- "actix-service 1.0.6",
- "log",
- "socket2 0.3.19",
-]
-
-[[package]]
-name = "actix-threadpool"
-version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d209f04d002854b9afd3743032a27b066158817965bf5d036824d19ac2cc0e30"
-dependencies = [
- "derive_more",
- "futures-channel",
- "lazy_static",
- "log",
- "num_cpus",
- "parking_lot 0.11.2",
- "threadpool",
-]
-
-[[package]]
-name = "actix-tls"
-version = "2.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24789b7d7361cf5503a504ebe1c10806896f61e96eca9a7350e23001aca715fb"
-dependencies = [
- "actix-codec 0.3.0",
- "actix-service 1.0.6",
- "actix-utils 2.0.0",
- "futures-util",
-]
-
-[[package]]
-name = "actix-utils"
-version = "2.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e9022dec56632d1d7979e59af14f0597a28a830a9c1c7fec8b2327eb9f16b5a"
-dependencies = [
- "actix-codec 0.3.0",
- "actix-rt 1.1.1",
- "actix-service 1.0.6",
- "bitflags 1.3.2",
- "bytes 0.5.6",
- "either",
- "futures-channel",
- "futures-sink",
- "futures-util",
- "log",
- "pin-project 0.4.30",
- "slab",
+ "pin-project-lite",
 ]
 
 [[package]]
@@ -363,46 +153,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8"
 dependencies = [
  "local-waker",
- "pin-project-lite 0.2.13",
-]
-
-[[package]]
-name = "actix-web"
-version = "3.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6534a126df581caf443ba2751cab42092c89b3f1d06a9d829b1e17edfe3e277"
-dependencies = [
- "actix-codec 0.3.0",
- "actix-http 2.2.2",
- "actix-macros 0.1.3",
- "actix-router 0.2.7",
- "actix-rt 1.1.1",
- "actix-server 1.0.4",
- "actix-service 1.0.6",
- "actix-testing",
- "actix-threadpool",
- "actix-tls",
- "actix-utils 2.0.0",
- "actix-web-codegen 0.4.0",
- "awc",
- "bytes 0.5.6",
- "derive_more",
- "encoding_rs",
- "futures-channel",
- "futures-core",
- "futures-util",
- "fxhash",
- "log",
- "mime",
- "pin-project 1.1.3",
- "regex",
- "serde",
- "serde_json",
- "serde_urlencoded",
- "socket2 0.3.19",
- "time 0.2.27",
- "tinyvec",
- "url",
+ "pin-project-lite",
 ]
 
 [[package]]
@@ -411,30 +162,30 @@ version = "4.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0e4a5b5e29603ca8c94a77c65cf874718ceb60292c5a5c3e5f4ace041af462b9"
 dependencies = [
- "actix-codec 0.5.1",
- "actix-http 3.4.0",
- "actix-macros 0.2.4",
- "actix-router 0.5.1",
- "actix-rt 2.9.0",
- "actix-server 2.3.0",
- "actix-service 2.0.2",
- "actix-utils 3.0.1",
- "actix-web-codegen 4.2.2",
+ "actix-codec",
+ "actix-http",
+ "actix-macros",
+ "actix-router",
+ "actix-rt",
+ "actix-server",
+ "actix-service",
+ "actix-utils",
+ "actix-web-codegen",
  "ahash 0.8.6",
- "bytes 1.5.0",
+ "bytes",
  "bytestring",
- "cfg-if 1.0.0",
- "cookie 0.16.2",
+ "cfg-if",
+ "cookie",
  "derive_more",
  "encoding_rs",
  "futures-core",
  "futures-util",
- "itoa 1.0.9",
- "language-tags 0.3.2",
+ "itoa",
+ "language-tags",
  "log",
  "mime",
  "once_cell",
- "pin-project-lite 0.2.13",
+ "pin-project-lite",
  "regex",
  "serde",
  "serde_json",
@@ -445,24 +196,13 @@ dependencies = [
  "url",
 ]
 
-[[package]]
-name = "actix-web-codegen"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ad26f77093333e0e7c6ffe54ebe3582d908a104e448723eec6d43d08b07143fb"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
 [[package]]
 name = "actix-web-codegen"
 version = "4.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "eb1f50ebbb30eca122b188319a4398b3f7bb4a8cdf50ecfb73bfc6a3c3ce54f5"
 dependencies = [
- "actix-router 0.5.1",
+ "actix-router",
  "proc-macro2",
  "quote",
  "syn 2.0.39",
@@ -474,13 +214,13 @@ version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1d613edf08a42ccc6864c941d30fe14e1b676a77d16f1dbadc1174d065a0a775"
 dependencies = [
- "actix-utils 3.0.1",
- "actix-web 4.4.0",
+ "actix-utils",
+ "actix-web",
  "base64 0.21.5",
  "futures-core",
  "futures-util",
  "log",
- "pin-project-lite 0.2.13",
+ "pin-project-lite",
 ]
 
 [[package]]
@@ -515,7 +255,7 @@ version = "0.8.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "const-random",
  "getrandom 0.2.11",
  "once_cell",
@@ -701,7 +441,7 @@ dependencies = [
  "event-listener 4.0.0",
  "event-listener-strategy",
  "futures-core",
- "pin-project-lite 0.2.13",
+ "pin-project-lite",
 ]
 
 [[package]]
@@ -752,7 +492,7 @@ checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af"
 dependencies = [
  "async-lock 2.8.0",
  "autocfg",
- "cfg-if 1.0.0",
+ "cfg-if",
  "concurrent-queue",
  "futures-lite 1.13.0",
  "log",
@@ -771,7 +511,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d6d3b15875ba253d1110c740755e246537483f152fa334f91abd7fe84c88b3ff"
 dependencies = [
  "async-lock 3.1.2",
- "cfg-if 1.0.0",
+ "cfg-if",
  "concurrent-queue",
  "futures-io",
  "futures-lite 2.0.1",
@@ -800,7 +540,7 @@ checksum = "dea8b3453dd7cc96711834b75400d671b73e3656975fa68d9f277163b7f7e316"
 dependencies = [
  "event-listener 4.0.0",
  "event-listener-strategy",
- "pin-project-lite 0.2.13",
+ "pin-project-lite",
 ]
 
 [[package]]
@@ -815,32 +555,6 @@ dependencies = [
  "reactor-trait",
 ]
 
-[[package]]
-name = "async-std"
-version = "1.12.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d"
-dependencies = [
- "async-channel 1.9.0",
- "async-global-executor",
- "async-io 1.13.0",
- "async-lock 2.8.0",
- "crossbeam-utils",
- "futures-channel",
- "futures-core",
- "futures-io",
- "futures-lite 1.13.0",
- "gloo-timers",
- "kv-log-macro",
- "log",
- "memchr",
- "once_cell",
- "pin-project-lite 0.2.13",
- "pin-utils",
- "slab",
- "wasm-bindgen-futures",
-]
-
 [[package]]
 name = "async-task"
 version = "4.5.0"
@@ -879,30 +593,6 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
-[[package]]
-name = "awc"
-version = "2.0.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b381e490e7b0cfc37ebc54079b0413d8093ef43d14a4e4747083f7fa47a9e691"
-dependencies = [
- "actix-codec 0.3.0",
- "actix-http 2.2.2",
- "actix-rt 1.1.1",
- "actix-service 1.0.6",
- "base64 0.13.1",
- "bytes 0.5.6",
- "cfg-if 1.0.0",
- "derive_more",
- "futures-core",
- "log",
- "mime",
- "percent-encoding",
- "rand 0.7.3",
- "serde",
- "serde_json",
- "serde_urlencoded",
-]
-
 [[package]]
 name = "backtrace"
 version = "0.3.69"
@@ -911,19 +601,13 @@ checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
 dependencies = [
  "addr2line",
  "cc",
- "cfg-if 1.0.0",
+ "cfg-if",
  "libc",
  "miniz_oxide",
  "object",
  "rustc-demangle",
 ]
 
-[[package]]
-name = "base-x"
-version = "0.2.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270"
-
 [[package]]
 name = "base64"
 version = "0.13.1"
@@ -948,15 +632,6 @@ version = "2.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
 
-[[package]]
-name = "block-buffer"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
-dependencies = [
- "generic-array",
-]
-
 [[package]]
 name = "block-buffer"
 version = "0.10.4"
@@ -1030,12 +705,6 @@ version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
 
-[[package]]
-name = "bytes"
-version = "0.5.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
-
 [[package]]
 name = "bytes"
 version = "1.5.0"
@@ -1048,7 +717,7 @@ version = "1.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "74d80203ea6b29df88012294f62733de21cfeab47f17b41af3a38bc30a03ee72"
 dependencies = [
- "bytes 1.5.0",
+ "bytes",
 ]
 
 [[package]]
@@ -1077,7 +746,7 @@ checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa"
 dependencies = [
  "camino",
  "cargo-platform",
- "semver 1.0.20",
+ "semver",
  "serde",
  "serde_json",
 ]
@@ -1088,7 +757,6 @@ version = "2.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "53fd06051bc34aa7ee753647dbd36506fa9f84026993d9871e65e0ee71ac3632"
 dependencies = [
- "async-std",
  "async-trait",
  "fixedbitset",
  "getrandom 0.2.11",
@@ -1101,6 +769,7 @@ dependencies = [
  "ritelinked",
  "serde",
  "thiserror",
+ "tokio",
 ]
 
 [[package]]
@@ -1122,12 +791,6 @@ dependencies = [
  "libc",
 ]
 
-[[package]]
-name = "cfg-if"
-version = "0.1.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
-
 [[package]]
 name = "cfg-if"
 version = "1.0.0"
@@ -1254,29 +917,12 @@ dependencies = [
  "tiny-keccak",
 ]
 
-[[package]]
-name = "const_fn"
-version = "0.4.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935"
-
 [[package]]
 name = "convert_case"
 version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
 
-[[package]]
-name = "cookie"
-version = "0.14.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951"
-dependencies = [
- "percent-encoding",
- "time 0.2.27",
- "version_check",
-]
-
 [[package]]
 name = "cookie"
 version = "0.16.2"
@@ -1294,12 +940,6 @@ version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b"
 
-[[package]]
-name = "copyless"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536"
-
 [[package]]
 name = "core-foundation"
 version = "0.9.3"
@@ -1346,7 +986,7 @@ version = "1.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
 ]
 
 [[package]]
@@ -1355,7 +995,7 @@ version = "0.5.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "crossbeam-utils",
 ]
 
@@ -1365,7 +1005,7 @@ version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "crossbeam-utils",
 ]
 
@@ -1375,7 +1015,7 @@ version = "0.8.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
 ]
 
 [[package]]
@@ -1435,7 +1075,7 @@ version = "5.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "hashbrown 0.14.2",
  "lock_api",
  "once_cell",
@@ -1452,7 +1092,7 @@ dependencies = [
  "deadpool-runtime",
  "num_cpus",
  "retain_mut",
- "tokio 1.34.0",
+ "tokio",
 ]
 
 [[package]]
@@ -1464,7 +1104,7 @@ dependencies = [
  "async-trait",
  "deadpool-runtime",
  "num_cpus",
- "tokio 1.34.0",
+ "tokio",
 ]
 
 [[package]]
@@ -1484,7 +1124,7 @@ version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "63dfa964fe2a66f3fde91fc70b267fe193d822c7e603e2a675a49a7f46ad3f49"
 dependencies = [
- "tokio 1.34.0",
+ "tokio",
 ]
 
 [[package]]
@@ -1536,7 +1176,7 @@ dependencies = [
  "convert_case",
  "proc-macro2",
  "quote",
- "rustc_version 0.4.0",
+ "rustc_version",
  "syn 1.0.109",
 ]
 
@@ -1549,22 +1189,13 @@ dependencies = [
  "cipher",
 ]
 
-[[package]]
-name = "digest"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
-dependencies = [
- "generic-array",
-]
-
 [[package]]
 name = "digest"
 version = "0.10.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
 dependencies = [
- "block-buffer 0.10.4",
+ "block-buffer",
  "crypto-common",
  "subtle",
 ]
@@ -1586,15 +1217,9 @@ checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
 dependencies = [
  "libc",
  "redox_users",
- "winapi 0.3.9",
+ "winapi",
 ]
 
-[[package]]
-name = "discard"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
-
 [[package]]
 name = "dlv-list"
 version = "0.3.0"
@@ -1640,19 +1265,7 @@ version = "0.8.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
 dependencies = [
- "cfg-if 1.0.0",
-]
-
-[[package]]
-name = "enum-as-inner"
-version = "0.3.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "570d109b813e904becc80d8d5da38376818a143348413f7149f1340fe04754d4"
-dependencies = [
- "heck",
- "proc-macro2",
- "quote",
- "syn 1.0.109",
+ "cfg-if",
 ]
 
 [[package]]
@@ -1694,7 +1307,7 @@ checksum = "770d968249b5d99410d61f5bf89057f3199a077a04d087092f58e7d10692baae"
 dependencies = [
  "concurrent-queue",
  "parking",
- "pin-project-lite 0.2.13",
+ "pin-project-lite",
 ]
 
 [[package]]
@@ -1704,7 +1317,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3"
 dependencies = [
  "event-listener 4.0.0",
- "pin-project-lite 0.2.13",
+ "pin-project-lite",
 ]
 
 [[package]]
@@ -1761,7 +1374,7 @@ checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577"
 dependencies = [
  "futures-core",
  "futures-sink",
- "pin-project 1.1.3",
+ "pin-project",
  "spin 0.9.8",
 ]
 
@@ -1804,22 +1417,6 @@ dependencies = [
  "percent-encoding",
 ]
 
-[[package]]
-name = "fuchsia-zircon"
-version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
-dependencies = [
- "bitflags 1.3.2",
- "fuchsia-zircon-sys",
-]
-
-[[package]]
-name = "fuchsia-zircon-sys"
-version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
-
 [[package]]
 name = "futures"
 version = "0.3.29"
@@ -1890,7 +1487,7 @@ dependencies = [
  "futures-io",
  "memchr",
  "parking",
- "pin-project-lite 0.2.13",
+ "pin-project-lite",
  "waker-fn",
 ]
 
@@ -1905,7 +1502,7 @@ dependencies = [
  "futures-io",
  "memchr",
  "parking",
- "pin-project-lite 0.2.13",
+ "pin-project-lite",
 ]
 
 [[package]]
@@ -1950,20 +1547,11 @@ dependencies = [
  "futures-sink",
  "futures-task",
  "memchr",
- "pin-project-lite 0.2.13",
+ "pin-project-lite",
  "pin-utils",
  "slab",
 ]
 
-[[package]]
-name = "fxhash"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
-dependencies = [
- "byteorder",
-]
-
 [[package]]
 name = "generic-array"
 version = "0.14.7"
@@ -1981,7 +1569,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e"
 dependencies = [
  "libc",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -1990,7 +1578,7 @@ version = "0.1.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "libc",
  "wasi 0.9.0+wasi-snapshot-preview1",
 ]
@@ -2001,7 +1589,7 @@ version = "0.2.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "js-sys",
  "libc",
  "wasi 0.11.0+wasi-snapshot-preview1",
@@ -2020,45 +1608,13 @@ version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
 
-[[package]]
-name = "gloo-timers"
-version = "0.2.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c"
-dependencies = [
- "futures-channel",
- "futures-core",
- "js-sys",
- "wasm-bindgen",
-]
-
-[[package]]
-name = "h2"
-version = "0.2.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e4728fd124914ad25e99e3d15a9361a879f6620f63cb56bbb08f95abb97a535"
-dependencies = [
- "bytes 0.5.6",
- "fnv",
- "futures-core",
- "futures-sink",
- "futures-util",
- "http",
- "indexmap 1.9.3",
- "slab",
- "tokio 0.2.25",
- "tokio-util 0.3.1",
- "tracing",
- "tracing-futures",
-]
-
 [[package]]
 name = "h2"
 version = "0.3.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178"
 dependencies = [
- "bytes 1.5.0",
+ "bytes",
  "fnv",
  "futures-core",
  "futures-sink",
@@ -2066,8 +1622,8 @@ dependencies = [
  "http",
  "indexmap 2.1.0",
  "slab",
- "tokio 1.34.0",
- "tokio-util 0.7.10",
+ "tokio",
+ "tokio-util",
  "tracing",
 ]
 
@@ -2144,18 +1700,7 @@ version = "0.12.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
 dependencies = [
- "digest 0.10.7",
-]
-
-[[package]]
-name = "hostname"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
-dependencies = [
- "libc",
- "match_cfg",
- "winapi 0.3.9",
+ "digest",
 ]
 
 [[package]]
@@ -2164,9 +1709,9 @@ version = "0.2.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb"
 dependencies = [
- "bytes 1.5.0",
+ "bytes",
  "fnv",
- "itoa 1.0.9",
+ "itoa",
 ]
 
 [[package]]
@@ -2175,9 +1720,9 @@ version = "0.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
 dependencies = [
- "bytes 1.5.0",
+ "bytes",
  "http",
- "pin-project-lite 0.2.13",
+ "pin-project-lite",
 ]
 
 [[package]]
@@ -2192,7 +1737,7 @@ dependencies = [
  "futures-lite 1.13.0",
  "http",
  "infer",
- "pin-project-lite 0.2.13",
+ "pin-project-lite",
  "rand 0.7.3",
  "serde",
  "serde_json",
@@ -2219,19 +1764,19 @@ version = "0.14.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468"
 dependencies = [
- "bytes 1.5.0",
+ "bytes",
  "futures-channel",
  "futures-core",
  "futures-util",
- "h2 0.3.22",
+ "h2",
  "http",
  "http-body",
  "httparse",
  "httpdate",
- "itoa 1.0.9",
- "pin-project-lite 0.2.13",
+ "itoa",
+ "pin-project-lite",
  "socket2 0.4.10",
- "tokio 1.34.0",
+ "tokio",
  "tower-service",
  "tracing",
  "want",
@@ -2243,10 +1788,10 @@ version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
 dependencies = [
- "bytes 1.5.0",
+ "bytes",
  "hyper",
  "native-tls",
- "tokio 1.34.0",
+ "tokio",
  "tokio-native-tls",
 ]
 
@@ -2279,17 +1824,6 @@ version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
 
-[[package]]
-name = "idna"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
-dependencies = [
- "matches",
- "unicode-bidi",
- "unicode-normalization",
-]
-
 [[package]]
 name = "idna"
 version = "0.5.0"
@@ -2344,7 +1878,7 @@ version = "0.1.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
 ]
 
 [[package]]
@@ -2358,27 +1892,6 @@ dependencies = [
  "windows-sys 0.48.0",
 ]
 
-[[package]]
-name = "iovec"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "ipconfig"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7"
-dependencies = [
- "socket2 0.3.19",
- "widestring",
- "winapi 0.3.9",
- "winreg 0.6.2",
-]
-
 [[package]]
 name = "ipnet"
 version = "2.9.0"
@@ -2403,12 +1916,6 @@ dependencies = [
  "either",
 ]
 
-[[package]]
-name = "itoa"
-version = "0.4.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
-
 [[package]]
 name = "itoa"
 version = "1.0.9"
@@ -2444,31 +1951,6 @@ dependencies = [
  "serde",
 ]
 
-[[package]]
-name = "kernel32-sys"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
-dependencies = [
- "winapi 0.2.8",
- "winapi-build",
-]
-
-[[package]]
-name = "kv-log-macro"
-version = "1.0.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f"
-dependencies = [
- "log",
-]
-
-[[package]]
-name = "language-tags"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
-
 [[package]]
 name = "language-tags"
 version = "0.3.2"
@@ -2571,24 +2053,6 @@ name = "log"
 version = "0.4.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
-dependencies = [
- "value-bag",
-]
-
-[[package]]
-name = "lru-cache"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
-dependencies = [
- "linked-hash-map",
-]
-
-[[package]]
-name = "match_cfg"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
 
 [[package]]
 name = "matchers"
@@ -2596,14 +2060,8 @@ version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
 dependencies = [
- "regex-automata 0.1.10",
-]
-
-[[package]]
-name = "matches"
-version = "0.1.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
+ "regex-automata 0.1.10",
+]
 
 [[package]]
 name = "md-5"
@@ -2611,8 +2069,8 @@ version = "0.10.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
 dependencies = [
- "cfg-if 1.0.0",
- "digest 0.10.7",
+ "cfg-if",
+ "digest",
 ]
 
 [[package]]
@@ -2657,25 +2115,6 @@ dependencies = [
  "adler",
 ]
 
-[[package]]
-name = "mio"
-version = "0.6.23"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4"
-dependencies = [
- "cfg-if 0.1.10",
- "fuchsia-zircon",
- "fuchsia-zircon-sys",
- "iovec",
- "kernel32-sys",
- "libc",
- "log",
- "miow",
- "net2",
- "slab",
- "winapi 0.2.8",
-]
-
 [[package]]
 name = "mio"
 version = "0.8.9"
@@ -2688,29 +2127,6 @@ dependencies = [
  "windows-sys 0.48.0",
 ]
 
-[[package]]
-name = "mio-uds"
-version = "0.6.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0"
-dependencies = [
- "iovec",
- "libc",
- "mio 0.6.23",
-]
-
-[[package]]
-name = "miow"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d"
-dependencies = [
- "kernel32-sys",
- "net2",
- "winapi 0.2.8",
- "ws2_32-sys",
-]
-
 [[package]]
 name = "mutually_exclusive_features"
 version = "0.0.3"
@@ -2735,17 +2151,6 @@ dependencies = [
  "tempfile",
 ]
 
-[[package]]
-name = "net2"
-version = "0.2.39"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b13b648036a2339d06de780866fbdfda0dde886de7b3af2ddeba8b14f4ee34ac"
-dependencies = [
- "cfg-if 0.1.10",
- "libc",
- "winapi 0.3.9",
-]
-
 [[package]]
 name = "nom"
 version = "7.1.3"
@@ -2763,7 +2168,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
 dependencies = [
  "overload",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -2800,12 +2205,6 @@ version = "1.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
 
-[[package]]
-name = "opaque-debug"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
-
 [[package]]
 name = "openssl"
 version = "0.10.60"
@@ -2813,7 +2212,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800"
 dependencies = [
  "bitflags 2.4.1",
- "cfg-if 1.0.0",
+ "cfg-if",
  "foreign-types",
  "libc",
  "once_cell",
@@ -2879,7 +2278,7 @@ dependencies = [
  "hmac",
  "lazy_static",
  "rc2",
- "sha1 0.10.6",
+ "sha1",
  "yasna",
 ]
 
@@ -2916,12 +2315,12 @@ version = "0.8.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "instant",
  "libc",
  "redox_syscall 0.2.16",
  "smallvec",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -2930,7 +2329,7 @@ version = "0.9.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "libc",
  "redox_syscall 0.4.1",
  "smallvec",
@@ -3010,33 +2409,13 @@ dependencies = [
  "indexmap 2.1.0",
 ]
 
-[[package]]
-name = "pin-project"
-version = "0.4.30"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ef0f924a5ee7ea9cbcea77529dba45f8a9ba9f622419fe3386ca581a3ae9d5a"
-dependencies = [
- "pin-project-internal 0.4.30",
-]
-
 [[package]]
 name = "pin-project"
 version = "1.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422"
 dependencies = [
- "pin-project-internal 1.1.3",
-]
-
-[[package]]
-name = "pin-project-internal"
-version = "0.4.30"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "851c8d0ce9bebe43790dedfc86614c23494ac9f423dd618d3a61fc693eafe61e"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 1.0.109",
+ "pin-project-internal",
 ]
 
 [[package]]
@@ -3050,12 +2429,6 @@ dependencies = [
  "syn 2.0.39",
 ]
 
-[[package]]
-name = "pin-project-lite"
-version = "0.1.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777"
-
 [[package]]
 name = "pin-project-lite"
 version = "0.2.13"
@@ -3105,11 +2478,11 @@ checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
 dependencies = [
  "autocfg",
  "bitflags 1.3.2",
- "cfg-if 1.0.0",
+ "cfg-if",
  "concurrent-queue",
  "libc",
  "log",
- "pin-project-lite 0.2.13",
+ "pin-project-lite",
  "windows-sys 0.48.0",
 ]
 
@@ -3119,9 +2492,9 @@ version = "3.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cf63fa624ab313c11656b4cda960bfc46c410187ad493c41f6ba2d8c1e991c9e"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "concurrent-queue",
- "pin-project-lite 0.2.13",
+ "pin-project-lite",
  "rustix 0.38.25",
  "tracing",
  "windows-sys 0.52.0",
@@ -3163,12 +2536,6 @@ dependencies = [
  "version_check",
 ]
 
-[[package]]
-name = "proc-macro-hack"
-version = "0.5.20+deprecated"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
-
 [[package]]
 name = "proc-macro2"
 version = "1.0.70"
@@ -3189,12 +2556,6 @@ dependencies = [
  "unicase",
 ]
 
-[[package]]
-name = "quick-error"
-version = "1.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
-
 [[package]]
 name = "quote"
 version = "1.0.33"
@@ -3375,11 +2736,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b"
 dependencies = [
  "base64 0.21.5",
- "bytes 1.5.0",
+ "bytes",
  "encoding_rs",
  "futures-core",
  "futures-util",
- "h2 0.3.22",
+ "h2",
  "http",
  "http-body",
  "hyper",
@@ -3391,29 +2752,19 @@ dependencies = [
  "native-tls",
  "once_cell",
  "percent-encoding",
- "pin-project-lite 0.2.13",
+ "pin-project-lite",
  "serde",
  "serde_json",
  "serde_urlencoded",
  "system-configuration",
- "tokio 1.34.0",
+ "tokio",
  "tokio-native-tls",
  "tower-service",
  "url",
  "wasm-bindgen",
  "wasm-bindgen-futures",
  "web-sys",
- "winreg 0.50.0",
-]
-
-[[package]]
-name = "resolv-conf"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00"
-dependencies = [
- "hostname",
- "quick-error",
+ "winreg",
 ]
 
 [[package]]
@@ -3462,7 +2813,7 @@ dependencies = [
  "spin 0.5.2",
  "untrusted 0.7.1",
  "web-sys",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -3506,7 +2857,7 @@ version = "0.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "ordered-multimap",
 ]
 
@@ -3516,22 +2867,13 @@ version = "0.1.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
 
-[[package]]
-name = "rustc_version"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
-dependencies = [
- "semver 0.9.0",
-]
-
 [[package]]
 name = "rustc_version"
 version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
 dependencies = [
- "semver 1.0.20",
+ "semver",
 ]
 
 [[package]]
@@ -3691,15 +3033,6 @@ dependencies = [
  "libc",
 ]
 
-[[package]]
-name = "semver"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
-dependencies = [
- "semver-parser",
-]
-
 [[package]]
 name = "semver"
 version = "1.0.20"
@@ -3709,12 +3042,6 @@ dependencies = [
  "serde",
 ]
 
-[[package]]
-name = "semver-parser"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
-
 [[package]]
 name = "serde"
 version = "1.0.193"
@@ -3741,7 +3068,7 @@ version = "1.0.108"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
 dependencies = [
- "itoa 1.0.9",
+ "itoa",
  "ryu",
  "serde",
 ]
@@ -3752,7 +3079,7 @@ version = "0.1.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335"
 dependencies = [
- "itoa 1.0.9",
+ "itoa",
  "serde",
 ]
 
@@ -3774,7 +3101,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
 dependencies = [
  "form_urlencoded",
- "itoa 1.0.9",
+ "itoa",
  "ryu",
  "serde",
 ]
@@ -3830,60 +3157,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c"
 dependencies = [
  "indexmap 2.1.0",
- "itoa 1.0.9",
+ "itoa",
  "ryu",
  "serde",
  "unsafe-libyaml",
 ]
 
-[[package]]
-name = "sha-1"
-version = "0.9.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6"
-dependencies = [
- "block-buffer 0.9.0",
- "cfg-if 1.0.0",
- "cpufeatures",
- "digest 0.9.0",
- "opaque-debug",
-]
-
-[[package]]
-name = "sha1"
-version = "0.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770"
-dependencies = [
- "sha1_smol",
-]
-
 [[package]]
 name = "sha1"
 version = "0.10.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "cpufeatures",
- "digest 0.10.7",
+ "digest",
 ]
 
-[[package]]
-name = "sha1_smol"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
-
 [[package]]
 name = "sha2"
 version = "0.10.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "cpufeatures",
- "digest 0.10.7",
+ "digest",
 ]
 
 [[package]]
@@ -3949,17 +3248,6 @@ dependencies = [
  "version_check",
 ]
 
-[[package]]
-name = "socket2"
-version = "0.3.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e"
-dependencies = [
- "cfg-if 1.0.0",
- "libc",
- "winapi 0.3.9",
-]
-
 [[package]]
 name = "socket2"
 version = "0.4.10"
@@ -3967,7 +3255,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
 dependencies = [
  "libc",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -4027,7 +3315,7 @@ dependencies = [
  "base64 0.13.1",
  "bitflags 1.3.2",
  "byteorder",
- "bytes 1.5.0",
+ "bytes",
  "chrono",
  "crc",
  "crossbeam-queue",
@@ -4044,7 +3332,7 @@ dependencies = [
  "hkdf",
  "hmac",
  "indexmap 1.9.3",
- "itoa 1.0.9",
+ "itoa",
  "libc",
  "log",
  "md-5",
@@ -4057,7 +3345,7 @@ dependencies = [
  "rustls-pemfile",
  "serde",
  "serde_json",
- "sha1 0.10.6",
+ "sha1",
  "sha2",
  "smallvec",
  "sqlformat",
@@ -4100,7 +3388,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "804d3f245f894e61b1e6263c84b23ca675d96753b5abfd5cc8597d86806e8024"
 dependencies = [
  "once_cell",
- "tokio 1.34.0",
+ "tokio",
  "tokio-rustls",
 ]
 
@@ -4110,8 +3398,8 @@ version = "0.1.0"
 dependencies = [
  "actix-casbin-auth",
  "actix-cors",
- "actix-http 3.4.0",
- "actix-web 4.4.0",
+ "actix-http",
+ "actix-web",
  "actix-web-httpauth",
  "chrono",
  "clap",
@@ -4138,7 +3426,7 @@ dependencies = [
  "sha2",
  "sqlx",
  "thiserror",
- "tokio 1.34.0",
+ "tokio",
  "tokio-stream",
  "tracing",
  "tracing-actix-web",
@@ -4149,70 +3437,12 @@ dependencies = [
  "wiremock",
 ]
 
-[[package]]
-name = "standback"
-version = "0.2.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff"
-dependencies = [
- "version_check",
-]
-
 [[package]]
 name = "static_assertions"
 version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
 
-[[package]]
-name = "stdweb"
-version = "0.4.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5"
-dependencies = [
- "discard",
- "rustc_version 0.2.3",
- "stdweb-derive",
- "stdweb-internal-macros",
- "stdweb-internal-runtime",
- "wasm-bindgen",
-]
-
-[[package]]
-name = "stdweb-derive"
-version = "0.5.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef"
-dependencies = [
- "proc-macro2",
- "quote",
- "serde",
- "serde_derive",
- "syn 1.0.109",
-]
-
-[[package]]
-name = "stdweb-internal-macros"
-version = "0.2.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11"
-dependencies = [
- "base-x",
- "proc-macro2",
- "quote",
- "serde",
- "serde_derive",
- "serde_json",
- "sha1 0.6.1",
- "syn 1.0.109",
-]
-
-[[package]]
-name = "stdweb-internal-runtime"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
-
 [[package]]
 name = "stringprep"
 version = "0.1.4"
@@ -4291,7 +3521,7 @@ version = "0.26.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4da30af7998f51ee1aa48ab24276fe303a697b004e31ff542b192c088d5630a5"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "p12",
  "rustls-connector",
  "rustls-pemfile",
@@ -4303,7 +3533,7 @@ version = "3.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "fastrand 2.0.1",
  "redox_syscall 0.4.1",
  "rustix 0.38.25",
@@ -4336,19 +3566,10 @@ version = "1.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "once_cell",
 ]
 
-[[package]]
-name = "threadpool"
-version = "1.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
-dependencies = [
- "num_cpus",
-]
-
 [[package]]
 name = "time"
 version = "0.1.45"
@@ -4357,22 +3578,7 @@ checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
 dependencies = [
  "libc",
  "wasi 0.10.0+wasi-snapshot-preview1",
- "winapi 0.3.9",
-]
-
-[[package]]
-name = "time"
-version = "0.2.27"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242"
-dependencies = [
- "const_fn",
- "libc",
- "standback",
- "stdweb",
- "time-macros 0.1.1",
- "version_check",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -4382,11 +3588,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
 dependencies = [
  "deranged",
- "itoa 1.0.9",
+ "itoa",
  "powerfmt",
  "serde",
  "time-core",
- "time-macros 0.2.15",
+ "time-macros",
 ]
 
 [[package]]
@@ -4395,16 +3601,6 @@ version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
 
-[[package]]
-name = "time-macros"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1"
-dependencies = [
- "proc-macro-hack",
- "time-macros-impl",
-]
-
 [[package]]
 name = "time-macros"
 version = "0.2.15"
@@ -4414,19 +3610,6 @@ dependencies = [
  "time-core",
 ]
 
-[[package]]
-name = "time-macros-impl"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f"
-dependencies = [
- "proc-macro-hack",
- "proc-macro2",
- "quote",
- "standback",
- "syn 1.0.109",
-]
-
 [[package]]
 name = "tiny-keccak"
 version = "2.0.2"
@@ -4451,26 +3634,6 @@ version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
 
-[[package]]
-name = "tokio"
-version = "0.2.25"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092"
-dependencies = [
- "bytes 0.5.6",
- "futures-core",
- "iovec",
- "lazy_static",
- "libc",
- "memchr",
- "mio 0.6.23",
- "mio-uds",
- "pin-project-lite 0.1.12",
- "signal-hook-registry",
- "slab",
- "winapi 0.3.9",
-]
-
 [[package]]
 name = "tokio"
 version = "1.34.0"
@@ -4478,12 +3641,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9"
 dependencies = [
  "backtrace",
- "bytes 1.5.0",
+ "bytes",
  "libc",
- "mio 0.8.9",
+ "mio",
  "num_cpus",
  "parking_lot 0.12.1",
- "pin-project-lite 0.2.13",
+ "pin-project-lite",
  "signal-hook-registry",
  "socket2 0.5.5",
  "tokio-macros",
@@ -4498,7 +3661,7 @@ checksum = "802ccf58e108fe16561f35348fabe15ff38218968f033d587e399a84937533cc"
 dependencies = [
  "async-trait",
  "executor-trait",
- "tokio 1.34.0",
+ "tokio",
 ]
 
 [[package]]
@@ -4519,7 +3682,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
 dependencies = [
  "native-tls",
- "tokio 1.34.0",
+ "tokio",
 ]
 
 [[package]]
@@ -4529,7 +3692,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
 dependencies = [
  "rustls 0.20.9",
- "tokio 1.34.0",
+ "tokio",
  "webpki",
 ]
 
@@ -4540,22 +3703,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842"
 dependencies = [
  "futures-core",
- "pin-project-lite 0.2.13",
- "tokio 1.34.0",
-]
-
-[[package]]
-name = "tokio-util"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499"
-dependencies = [
- "bytes 0.5.6",
- "futures-core",
- "futures-sink",
- "log",
- "pin-project-lite 0.1.12",
- "tokio 0.2.25",
+ "pin-project-lite",
+ "tokio",
 ]
 
 [[package]]
@@ -4564,11 +3713,11 @@ version = "0.7.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
 dependencies = [
- "bytes 1.5.0",
+ "bytes",
  "futures-core",
  "futures-sink",
- "pin-project-lite 0.2.13",
- "tokio 1.34.0",
+ "pin-project-lite",
+ "tokio",
  "tracing",
 ]
 
@@ -4594,7 +3743,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
 dependencies = [
  "log",
- "pin-project-lite 0.2.13",
+ "pin-project-lite",
  "tracing-attributes",
  "tracing-core",
 ]
@@ -4605,9 +3754,9 @@ version = "0.7.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1fe0d5feac3f4ca21ba33496bcb1ccab58cca6412b1405ae80f0581541e0ca78"
 dependencies = [
- "actix-web 4.4.0",
+ "actix-web",
  "mutually_exclusive_features",
- "pin-project 1.1.3",
+ "pin-project",
  "tracing",
  "uuid",
 ]
@@ -4651,16 +3800,6 @@ dependencies = [
  "valuable",
 ]
 
-[[package]]
-name = "tracing-futures"
-version = "0.2.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2"
-dependencies = [
- "pin-project 1.1.3",
- "tracing",
-]
-
 [[package]]
 name = "tracing-log"
 version = "0.1.4"
@@ -4707,45 +3846,6 @@ version = "0.1.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3"
 
-[[package]]
-name = "trust-dns-proto"
-version = "0.19.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1cad71a0c0d68ab9941d2fb6e82f8fb2e86d9945b94e1661dd0aaea2b88215a9"
-dependencies = [
- "async-trait",
- "cfg-if 1.0.0",
- "enum-as-inner",
- "futures",
- "idna 0.2.3",
- "lazy_static",
- "log",
- "rand 0.7.3",
- "smallvec",
- "thiserror",
- "tokio 0.2.25",
- "url",
-]
-
-[[package]]
-name = "trust-dns-resolver"
-version = "0.19.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "710f593b371175db53a26d0b38ed2978fafb9e9e8d3868b1acd753ea18df0ceb"
-dependencies = [
- "cfg-if 0.1.10",
- "futures",
- "ipconfig",
- "lazy_static",
- "log",
- "lru-cache",
- "resolv-conf",
- "smallvec",
- "thiserror",
- "tokio 0.2.25",
- "trust-dns-proto",
-]
-
 [[package]]
 name = "try-lock"
 version = "0.2.4"
@@ -4831,7 +3931,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
 dependencies = [
  "form_urlencoded",
- "idna 0.5.0",
+ "idna",
  "percent-encoding",
  "serde",
 ]
@@ -4858,12 +3958,6 @@ version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
 
-[[package]]
-name = "value-bag"
-version = "1.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7cdbaf5e132e593e9fc1de6a15bbec912395b11fb9719e061cf64f804524c503"
-
 [[package]]
 name = "vcpkg"
 version = "0.2.15"
@@ -4925,7 +4019,7 @@ version = "0.2.88"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "wasm-bindgen-macro",
 ]
 
@@ -4950,7 +4044,7 @@ version = "0.4.38"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "js-sys",
  "wasm-bindgen",
  "web-sys",
@@ -5024,18 +4118,6 @@ dependencies = [
  "web-sys",
 ]
 
-[[package]]
-name = "widestring"
-version = "0.4.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c"
-
-[[package]]
-name = "winapi"
-version = "0.2.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
-
 [[package]]
 name = "winapi"
 version = "0.3.9"
@@ -5046,12 +4128,6 @@ dependencies = [
  "winapi-x86_64-pc-windows-gnu",
 ]
 
-[[package]]
-name = "winapi-build"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
-
 [[package]]
 name = "winapi-i686-pc-windows-gnu"
 version = "0.4.0"
@@ -5064,7 +4140,7 @@ version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
 dependencies = [
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -5214,22 +4290,13 @@ version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
 
-[[package]]
-name = "winreg"
-version = "0.6.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9"
-dependencies = [
- "winapi 0.3.9",
-]
-
 [[package]]
 name = "winreg"
 version = "0.50.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "windows-sys 0.48.0",
 ]
 
@@ -5252,17 +4319,7 @@ dependencies = [
  "regex",
  "serde",
  "serde_json",
- "tokio 1.34.0",
-]
-
-[[package]]
-name = "ws2_32-sys"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
-dependencies = [
- "winapi 0.2.8",
- "winapi-build",
+ "tokio",
 ]
 
 [[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 024d61bf..abf15fc8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -55,7 +55,7 @@ clap = { version = "4.4.8", features = ["derive"] }
 serde_path_to_error = "0.1.14"
 deadpool-lapin = "0.11.0"
 docker-compose-types = "0.7.0"
-actix-casbin-auth = "0.4.4"
+actix-casbin-auth = { git = "https://github.com/casbin-rs/actix-casbin-auth.git"}
 
 [dependencies.sqlx]
 version = "0.6.3"
diff --git a/rbac/rbac_with_pattern_model.conf b/rbac/rbac_with_pattern_model.conf
new file mode 100644
index 00000000..44507a61
--- /dev/null
+++ b/rbac/rbac_with_pattern_model.conf
@@ -0,0 +1,15 @@
+[request_definition]
+r = sub, obj, act
+
+[policy_definition]
+p = sub, obj, act
+
+[role_definition]
+g = _, _
+g2 = _, _
+
+[policy_effect]
+e = some(where (p.eft == allow))
+
+[matchers]
+m = g(r.sub, p.sub) && g2(r.obj, p.obj) && regexMatch(r.act, p.act)
diff --git a/rbac/rbac_with_pattern_policy.csv b/rbac/rbac_with_pattern_policy.csv
new file mode 100644
index 00000000..ea5b1a61
--- /dev/null
+++ b/rbac/rbac_with_pattern_policy.csv
@@ -0,0 +1,10 @@
+p, petru, /testcasbin, GET
+p, alice, /pen/1, GET
+p, alice, /pen2/1, GET
+p, book_admin, book_group, GET
+p, pen_admin, pen_group, GET
+
+g, alice, book_admin
+g, bob, pen_admin
+g2, /book/:id, book_group
+g2, /pen/:id, pen_group
diff --git a/src/middleware/trydirect.rs b/src/middleware/trydirect.rs
index c079635f..33c54a73 100644
--- a/src/middleware/trydirect.rs
+++ b/src/middleware/trydirect.rs
@@ -20,6 +20,13 @@ pub async fn bearer_guard( req: ServiceRequest, credentials: BearerAuth) -> Resu
         return Err((JsonResponse::<i32>::build().unauthorized("user already logged"), req));
     }
 
+    let vals = actix_casbin_auth::CasbinVals { //todo
+        subject: String::from("alice"),
+        domain: Some("/pen/1".to_string()),
+    };
+    let result = req.extensions_mut().insert(vals); //todo
+    tracing::error!("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA {}", result.is_some()); //todo
+
     Ok(req)
 }
 
diff --git a/src/routes/test/casbin.rs b/src/routes/test/casbin.rs
new file mode 100644
index 00000000..77e1ec59
--- /dev/null
+++ b/src/routes/test/casbin.rs
@@ -0,0 +1,8 @@
+use actix_web::{get, Responder, Result};
+use crate::helpers::JsonResponse;
+
+#[tracing::instrument(name = "Test casbin.")]
+#[get("")]
+pub async fn handler() -> Result<impl Responder> {
+    Ok(JsonResponse::<i32>::build().ok("success"))
+}
diff --git a/src/routes/test/deploy.rs b/src/routes/test/deploy.rs
index 4f36a3ae..f123417a 100644
--- a/src/routes/test/deploy.rs
+++ b/src/routes/test/deploy.rs
@@ -1,15 +1,8 @@
 use crate::models::Client;
 use actix_web::{post, web, Responder, Result};
-use serde::Serialize;
 use std::sync::Arc;
 use crate::helpers::JsonResponse;
 
-#[derive(Serialize)]
-struct DeployResponse {
-    status: String,
-    client: Arc<Client>,
-}
-
 #[tracing::instrument(name = "Test deploy.")]
 #[post("/deploy")]
 pub async fn handler(client: web::ReqData<Arc<Client>>) -> Result<impl Responder> {
diff --git a/src/routes/test/mod.rs b/src/routes/test/mod.rs
index 40149b14..2b55b427 100644
--- a/src/routes/test/mod.rs
+++ b/src/routes/test/mod.rs
@@ -1 +1,2 @@
 pub mod deploy;
+pub mod casbin;
diff --git a/src/startup.rs b/src/startup.rs
index b510af64..f00d0d83 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -11,6 +11,11 @@ use sqlx::{Pool, Postgres};
 use std::net::TcpListener;
 use tracing_actix_web::TracingLogger;
 
+use actix_casbin_auth::casbin::{DefaultModel, FileAdapter};//, Result};
+use actix_casbin_auth::CasbinService;
+use actix_casbin_auth::casbin::function_map::key_match2;
+use actix_casbin_auth::casbin::CoreApi;
+
 pub async fn run(
     listener: TcpListener,
     pg_pool: Pool<Postgres>,
@@ -22,6 +27,22 @@ pub async fn run(
     let mq_manager = helpers::MqManager::try_new(settings.amqp.connection_string())?;
     let mq_manager = web::Data::new(mq_manager);
 
+    //casbin
+     let m = DefaultModel::from_file("rbac/rbac_with_pattern_model.conf")
+        .await
+        .unwrap();
+    let a = FileAdapter::new("rbac/rbac_with_pattern_policy.csv");  //You can also use diesel-adapter or sqlx-adapter
+
+    let casbin_middleware = CasbinService::new(m, a).await.unwrap(); //todo
+
+    casbin_middleware
+        .write()
+        .await
+        .get_role_manager()
+        .write()
+        //.unwrap()
+        .matching_fn(Some(key_match2), None);
+
     let server = HttpServer::new(move || {
         App::new()
             .wrap(TracingLogger::default())
@@ -39,10 +60,20 @@ pub async fn run(
             )
             .service(
                 web::scope("/test")
+                    .wrap(casbin_middleware.clone())
                     .wrap(crate::middleware::client::Guard::new())
                     .wrap(Cors::permissive())
                     .service(crate::routes::test::deploy::handler),
             )
+            .service(
+                web::scope("/pen/1")
+                    .wrap(casbin_middleware.clone()) //todo
+                    .wrap(HttpAuthentication::bearer(
+                        crate::middleware::trydirect::bearer_guard,
+                    ))
+                    .wrap(Cors::permissive())
+                    .service(crate::routes::test::casbin::handler),
+            )
             .service(
                 web::scope("/rating")
                     .wrap(HttpAuthentication::bearer(

From 1833e8fafee6010c2846d8712692c2e4d9fb9864 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Wed, 24 Jan 2024 15:12:19 +0200
Subject: [PATCH 152/284] test fixes

---
 src/forms/stack.rs           |  43 +++++++-------
 src/helpers/dockerhub.rs     |  32 +++++-----
 src/helpers/mod.rs           |   2 +-
 src/helpers/stack/builder.rs |  44 ++++++++++----
 src/helpers/stack/mod.rs     |   2 +-
 tests/dockerhub.rs           | 110 ++++++++++++++++-------------------
 6 files changed, 125 insertions(+), 108 deletions(-)

diff --git a/src/forms/stack.rs b/src/forms/stack.rs
index 07674fa3..135d0d0b 100644
--- a/src/forms/stack.rs
+++ b/src/forms/stack.rs
@@ -3,7 +3,7 @@ use serde_json::Value;
 use serde_valid::Validate;
 use std::fmt;
 use regex::Regex;
-use crate::helpers::dockerhub::{DockerHub, DockerHubCreds, DockerHubToken};
+use crate::helpers::dockerhub::DockerHub;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Role {
@@ -102,7 +102,7 @@ impl fmt::Display for DockerImage {
 
 impl DockerImage {
     #[tracing::instrument(name = "is_active")]
-    async fn is_active(&self) -> Result<bool, String> {
+    pub async fn is_active(&self) -> Result<bool, String> {
         DockerHub::from(self).is_active().await
     }
 }
@@ -170,23 +170,24 @@ impl StackForm {
             }
         }
 
-        if let Some(service) = &self.custom.service {
-            for app in service {
-                if !app.app.docker_image.is_active().await? {
-                    is_active = false;
-                    break;
-                }
-            }
-        }
-
-        if let Some(features) = &self.custom.feature {
-            for app in features {
-                if !app.app.docker_image.is_active().await? {
-                    is_active = false;
-                    break;
-                }
-            }
-        }
+        // temporarily disabled
+        // if let Some(service) = &self.custom.service {
+        //     for app in service {
+        //         if !app.app.docker_image.is_active().await? {
+        //             is_active = false;
+        //             break;
+        //         }
+        //     }
+        // }
+        //
+        // if let Some(features) = &self.custom.feature {
+        //     for app in features {
+        //         if !app.app.docker_image.is_active().await? {
+        //             is_active = false;
+        //             break;
+        //         }
+        //     }
+        // }
         Ok(is_active)
     }
 }
@@ -350,8 +351,8 @@ pub struct Environment {
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct Volume {
-    pub(crate) host_path: Option<String>,
-    pub(crate) container_path: Option<String>,
+    pub host_path: Option<String>,
+    pub container_path: Option<String>,
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
diff --git a/src/helpers/dockerhub.rs b/src/helpers/dockerhub.rs
index e93783a2..a4af31c8 100644
--- a/src/helpers/dockerhub.rs
+++ b/src/helpers/dockerhub.rs
@@ -52,17 +52,17 @@ struct Tag {
 }
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 struct TagResult {
-    count: i64,
-    next: Value,
-    previous: Value,
+    pub count: Option<i64>,
+    next: Option<Value>,
+    previous: Option<Value>,
     results: Vec<Tag>
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct RepoResults {
-    pub count: i64,
-    pub next: Value,
-    pub previous: Value,
+    pub count: Option<i64>,
+    pub next: Option<Value>,
+    pub previous: Option<Value>,
     pub results: Vec<RepoResult>,
 }
 
@@ -85,7 +85,7 @@ pub struct RepoResult {
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Validate)]
-pub(crate) struct DockerHub<'a> {
+pub struct DockerHub<'a> {
     pub(crate) creds: DockerHubCreds<'a>,
     pub(crate) repos: String,
     pub(crate) image: String,
@@ -123,10 +123,10 @@ impl<'a> DockerHub<'a> {
             })?
             .json::<RepoResults>()
             .await
-            .map_err(|err| format!("{}", err))
+            .map_err(|err| format!("Error on getting results:: {}", err))
             .map(|repositories| {
                 tracing::debug!("Get public image repositories response {:?}", repositories);
-                if repositories.count > 0 {
+                if repositories.count.unwrap_or(0) > 0 {
                     // let's find at least one active tag
                     let active = repositories.results
                         .into_iter()
@@ -159,7 +159,7 @@ impl<'a> DockerHub<'a> {
             })
             .map(|tags| {
                 tracing::debug!("Validate private image response {:?}", tags);
-                if tags.count > 0 {
+                if tags.count.unwrap_or(0) > 0 {
                     // let's find at least one active tag
                     let active = tags.results
                         .into_iter()
@@ -174,10 +174,16 @@ impl<'a> DockerHub<'a> {
 
         // if namespace/user is not set change endpoint and return a different response
         if self.creds.username.is_empty() {
-            return self.lookup_public_repo().await;
+            match self.lookup_public_repo().await {
+               Ok(result) => Ok(result),
+               Err(_e) => Ok(false)
+            }
+        } else{
+            match self.lookup_private_repo().await {
+                Ok(result) => Ok(result),
+                Err(_e) => Ok(false)
+            }
         }
-        return self.lookup_private_repo().await;
-
     }
 
     pub async fn set_token(&self, mut client: RequestBuilder) -> Result<RequestBuilder, String> {
diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs
index 3fe765fb..e02a2c76 100644
--- a/src/helpers/mod.rs
+++ b/src/helpers/mod.rs
@@ -1,6 +1,6 @@
 pub mod client;
 pub(crate) mod json;
-pub(crate) mod stack;
+pub mod stack;
 pub use json::*;
 pub(crate) mod compressor;
 pub mod dockerhub;
diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index 501d22ed..1d7868a3 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -5,7 +5,7 @@ use crate::helpers::stack::dctypes::{Compose, Port, Ports, PublishedPort, Servic
                                      ComposeNetworks, MapOrEmpty, ComposeNetworkSettingDetails,
                                      NetworkSettings};
 use serde_yaml;
-use crate::forms::{StackForm, stack, App, Volume, Web};
+use crate::forms::{StackForm, stack, App, Volume};
 use crate::models::stack::Stack;
 #[derive(Clone, Debug)]
 struct Config {}
@@ -36,16 +36,23 @@ pub struct DcBuilder {
 }
 
 impl TryInto<AdvancedVolumes> for Volume {
+    // service volumes
     type Error = String;
     fn try_into(self) -> Result<AdvancedVolumes, Self::Error> {
 
         let source = self.host_path.clone();
         let target = self.container_path.clone();
         tracing::debug!("Volume conversion result: source: {:?} target: {:?}", source, target);
+        let _type = if is_named_docker_volume(source.as_ref().unwrap()) {
+            "volume"
+        } else {
+            "bind"
+        };
+
         Ok(AdvancedVolumes {
-            source: source,
+            source,
             target: target.unwrap_or("".to_string()),
-            _type: "".to_string(),
+            _type: _type.to_string(),
             read_only: false,
             bind: None,
             volume: None,
@@ -216,7 +223,28 @@ impl Into<IndexMap<String, MapOrEmpty<NetworkSettings>>> for stack::ComposeNetwo
     }
 }
 
+impl Into<ComposeVolume> for Volume {
+    fn into(self) -> ComposeVolume {
+        // let's create a symlink to /var/docker/volumes in project docroot
+        let mut driver_opts = IndexMap::default();
+        let host_path = self.host_path.unwrap();
+        driver_opts.insert(String::from("type"), Some(SingleValue::String("none".to_string())));
+        driver_opts.insert(String::from("o"), Some(SingleValue::String("bind".to_string())));
+        // @todo move to config stack docroot on host
+        let path = format!("/root/stack/{}", &host_path);
+        driver_opts.insert(String::from("device"), Some(SingleValue::String(path)));
+
+        ComposeVolume {
+            driver: Some(String::from("local")),
+            driver_opts: driver_opts,
+            external: None,
+            labels: Default::default(),
+            name: Some(host_path)
+        }
+    }
+}
 
+#[tracing::instrument(name = "extract_named_volumes")]
 pub fn extract_named_volumes(app: App) -> IndexMap<String, MapOrEmpty<ComposeVolume>> {
 
     let mut named_volumes = IndexMap::default();
@@ -232,18 +260,12 @@ pub fn extract_named_volumes(app: App) -> IndexMap<String, MapOrEmpty<ComposeVol
         )
         .map(|volume| {
             let k = volume.host_path.clone().unwrap();
-            (k.clone(), MapOrEmpty::Map(ComposeVolume {
-                driver: None,
-                driver_opts: Default::default(),
-                external: None,
-                labels: Default::default(),
-                name: Some(k.clone())
-            }))
+            (k.clone(), MapOrEmpty::Map(volume.into()))
         })
         .collect::<IndexMap<String, MapOrEmpty<ComposeVolume>>>();
 
     named_volumes.extend(volumes);
-    // tracing::debug!("Named volumes: {:?}", named_volumes);
+    tracing::debug!("Named volumes: {:?}", named_volumes);
 
     named_volumes
 }
diff --git a/src/helpers/stack/mod.rs b/src/helpers/stack/mod.rs
index 7da2d2ec..6f09fdba 100644
--- a/src/helpers/stack/mod.rs
+++ b/src/helpers/stack/mod.rs
@@ -1,2 +1,2 @@
 pub(crate) mod builder;
-pub(crate) mod dctypes;
\ No newline at end of file
+pub mod dctypes;
\ No newline at end of file
diff --git a/tests/dockerhub.rs b/tests/dockerhub.rs
index e13fe555..f1095822 100644
--- a/tests/dockerhub.rs
+++ b/tests/dockerhub.rs
@@ -2,11 +2,13 @@
 // use std::collections::HashMap;
 use std::env;
 mod common;
-use stacker::helpers::dockerhub::{login, docker_image_exists};
+use stacker::forms::stack::DockerImage;
+use stacker::helpers::stack::dctypes::{ComposeVolume, SingleValue};
+use serde_yaml;
+use stacker::forms::Volume;
 
 const DOCKER_USERNAME: &str = "trydirect";
-const DOCKER_PASSWORD: &str = "dckr_pat_NyQoqqA2VFtBtCuZRp8YcjbuFkU";
-
+const DOCKER_PASSWORD: &str = "*******";
 //  Unit Test
 
 // #[test]
@@ -54,7 +56,7 @@ const DOCKER_PASSWORD: &str = "dckr_pat_NyQoqqA2VFtBtCuZRp8YcjbuFkU";
 // }
 
 #[tokio::test]
-async fn test_docker_hub_login() {
+async fn test_docker_hub_successful_login() {
 
     common::spawn_app().await; // server
     // let username = env::var("TEST_DOCKER_USERNAME")
@@ -62,78 +64,64 @@ async fn test_docker_hub_login() {
     //
     // let password= env::var("TEST_DOCKER_PASSWORD")
     //     .expect("password environment variable is not set");
-    let result = login(
-        DOCKER_USERNAME.as_ref(),
-        DOCKER_PASSWORD.as_ref()
-    ).await;
-    let token = result.unwrap().token;
-
-    let exists: bool = match token {
-        Some(tok) => {
-            let exists = docker_image_exists(DOCKER_USERNAME, "nginx", tok).await;
-            match exists {
-                Ok(_) => true,
-                Err(err) => {
-                    println!("{:?}", err);
-                    false
-                }
-            }
-        }
-        _ => false,
+    let di = DockerImage {
+        dockerhub_user: Some(String::from("trydirect")),
+        dockerhub_name: Some(String::from("nginx-waf")),
+        dockerhub_image: None,
+        dockerhub_password: Some(String::from(DOCKER_PASSWORD))
     };
-
-    assert_eq!(exists, true);
+    assert_eq!(di.is_active().await.unwrap(), true);
 }
 
 #[tokio::test]
 async fn test_docker_private_exists() {
 
     common::spawn_app().await; // server
-    let result = login(
-        DOCKER_USERNAME.as_ref(), DOCKER_PASSWORD.as_ref()
-    ).await;
-    let token = result.unwrap().token;
-
-    let exists: bool = match token {
-        Some(tok) => {
-            let exists = docker_image_exists(DOCKER_USERNAME, "nginx-waf", tok).await;
-            match exists {
-                Ok(_) => true,
-                Err(err) => {
-                    println!("{:?}", err);
-                    false
-                }
-            }
-        }
-        _ => false,
+    let di = DockerImage {
+        dockerhub_user: Some(String::from("trydirect")),
+        dockerhub_name: Some(String::from("nginx-waf")),
+        dockerhub_image: None,
+        dockerhub_password: Some(String::from(DOCKER_PASSWORD))
     };
-
-    assert_eq!(exists, true);
+    assert_eq!(di.is_active().await.unwrap(), true);
 }
 
+#[tokio::test]
+async fn test_public_repo_is_accessible() {
+
+    common::spawn_app().await; // server
+    let di = DockerImage {
+        dockerhub_user: Some(String::from("")),
+        dockerhub_name: Some(String::from("nginx")),
+        dockerhub_image: None,
+        dockerhub_password: Some(String::from(""))
+    };
+    assert_eq!(di.is_active().await.unwrap(), true);
+}
 #[tokio::test]
 async fn test_docker_non_existent_repo() {
 
     common::spawn_app().await; // server
-    let result= login(
-        DOCKER_USERNAME.as_ref(), DOCKER_PASSWORD.as_ref()
-    ).await;
-    let token = result.unwrap().token;
+    let di = DockerImage {
+        dockerhub_user: Some(String::from("trydirect")),
+        dockerhub_name: Some(String::from("nonexistent")),
+        dockerhub_image: None,
+        dockerhub_password: Some(String::from(""))
+    };
+    assert_eq!(di.is_active().await.unwrap(), false);
+}
 
-    // println!("{:?}", token);
-    let exists: bool = match token {
-        Some(tok) => {
-            let exists = docker_image_exists(DOCKER_USERNAME, "nonexistent", tok).await;
-            match exists {
-                Ok(_) => true,
-                Err(err) => {
-                    println!("{:?}", err);
-                    false
-                }
-            }
-        }
-        _ => false,
+#[tokio::test]
+async fn test_docker_named_volume() {
+    let volume = Volume {
+        host_path: Some("flask-data".to_owned()),
+        container_path: Some("/var/www/flaskdata".to_owned()),
     };
 
-    assert_eq!(exists, false);
+    let cv:ComposeVolume = volume.into();
+    println!("ComposeVolume: {:?}", cv);
+    println!("{:?}", cv.driver_opts);
+    assert_eq!(Some("flask-data".to_string()), cv.name);
+    assert_eq!(&Some(SingleValue::String("/root/stack/flask-data".to_string())), cv.driver_opts.get("device").unwrap());
+    assert_eq!(&Some(SingleValue::String("none".to_string())), cv.driver_opts.get("type").unwrap());
 }

From 3286ae59e77699569ff0c29a9bdfc47a8320bc17 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Wed, 24 Jan 2024 19:47:40 +0200
Subject: [PATCH 153/284] restart policy fix

---
 src/forms/stack.rs           | 3 ++-
 src/helpers/stack/builder.rs | 2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/forms/stack.rs b/src/forms/stack.rs
index 135d0d0b..fe04381c 100644
--- a/src/forms/stack.rs
+++ b/src/forms/stack.rs
@@ -327,7 +327,8 @@ pub struct App {
     pub repo_dir: Option<String>,
     pub url_app: Option<String>,
     pub url_git: Option<String>,
-    pub restart: Option<String>,
+    #[validate(enumerate("always", "no", "unless-stopped", "on-failure"))]
+    pub restart: String,
     pub volumes: Option<Vec<Volume>>,
     #[serde(flatten)]
     pub environment: Environment,
diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index 1d7868a3..23d1bd91 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -170,7 +170,7 @@ impl TryIntoService for App {
 
         service.networks = networks;
         service.ports = Ports::Long(ports);
-        service.restart = Some("always".to_owned());
+        service.restart = Some(self.restart.clone());
         service.volumes = Volumes::Advanced(volumes);
         service.environment = Environment::KvPair(envs);
 

From 1045db8762a93a51a2d385e57ee043d0b1690857 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 27 Jan 2024 23:18:22 +0200
Subject: [PATCH 154/284] issue-auth casbin.1

---
 Cargo.lock                        | 642 ++++++++++++++++--------------
 Cargo.toml                        |   3 +-
 rbac/rbac_with_pattern_model.conf |   9 +-
 rbac/rbac_with_pattern_policy.csv |  16 +-
 src/middleware/trydirect.rs       |   2 +-
 src/startup.rs                    |   3 +-
 6 files changed, 347 insertions(+), 328 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 35a44030..c4ceaf8a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5,7 +5,7 @@ version = 3
 [[package]]
 name = "actix-casbin-auth"
 version = "0.4.4"
-source = "git+https://github.com/casbin-rs/actix-casbin-auth.git#5f8550953a29460d18029fb50785773d6957d717"
+source = "git+https://github.com/smart--petea/actix-casbin-auth.git?branch=dirty-master#795c1209e4dea6078a787ccf7a882bd5d1148be7"
 dependencies = [
  "actix-service",
  "actix-web",
@@ -33,9 +33,9 @@ dependencies = [
 
 [[package]]
 name = "actix-cors"
-version = "0.6.4"
+version = "0.6.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b340e9cfa5b08690aae90fb61beb44e9b06f44fe3d0f93781aaa58cfba86245e"
+checksum = "0346d8c1f762b41b458ed3145eea914966bb9ad20b9be0d6d463b20d45586370"
 dependencies = [
  "actix-utils",
  "actix-web",
@@ -48,17 +48,17 @@ dependencies = [
 
 [[package]]
 name = "actix-http"
-version = "3.4.0"
+version = "3.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a92ef85799cba03f76e4f7c10f533e66d87c9a7e7055f3391f09000ad8351bc9"
+checksum = "129d4c88e98860e1758c5de288d1632b07970a16d59bdf7b8d66053d582bb71f"
 dependencies = [
  "actix-codec",
  "actix-rt",
  "actix-service",
  "actix-utils",
- "ahash 0.8.6",
- "base64 0.21.5",
- "bitflags 2.4.1",
+ "ahash 0.8.7",
+ "base64 0.21.7",
+ "bitflags 2.4.2",
  "brotli",
  "bytes",
  "bytestring",
@@ -92,14 +92,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb"
 dependencies = [
  "quote",
- "syn 2.0.39",
+ "syn 2.0.48",
 ]
 
 [[package]]
 name = "actix-router"
-version = "0.5.1"
+version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d66ff4d247d2b160861fa2866457e85706833527840e4133f8f49aa423a38799"
+checksum = "d22475596539443685426b6bdadb926ad0ecaefdfc5fb05e5e3441f15463c511"
 dependencies = [
  "bytestring",
  "http",
@@ -158,9 +158,9 @@ dependencies = [
 
 [[package]]
 name = "actix-web"
-version = "4.4.0"
+version = "4.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e4a5b5e29603ca8c94a77c65cf874718ceb60292c5a5c3e5f4ace041af462b9"
+checksum = "e43428f3bf11dee6d166b00ec2df4e3aa8cc1606aaa0b7433c146852e2f4e03b"
 dependencies = [
  "actix-codec",
  "actix-http",
@@ -171,7 +171,7 @@ dependencies = [
  "actix-service",
  "actix-utils",
  "actix-web-codegen",
- "ahash 0.8.6",
+ "ahash 0.8.7",
  "bytes",
  "bytestring",
  "cfg-if",
@@ -192,7 +192,7 @@ dependencies = [
  "serde_urlencoded",
  "smallvec",
  "socket2 0.5.5",
- "time 0.3.30",
+ "time 0.3.31",
  "url",
 ]
 
@@ -205,7 +205,7 @@ dependencies = [
  "actix-router",
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.48",
 ]
 
 [[package]]
@@ -216,7 +216,7 @@ checksum = "1d613edf08a42ccc6864c941d30fe14e1b676a77d16f1dbadc1174d065a0a775"
 dependencies = [
  "actix-utils",
  "actix-web",
- "base64 0.21.5",
+ "base64 0.21.7",
  "futures-core",
  "futures-util",
  "log",
@@ -244,20 +244,20 @@ version = "0.7.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd"
 dependencies = [
- "getrandom 0.2.11",
+ "getrandom 0.2.12",
  "once_cell",
  "version_check",
 ]
 
 [[package]]
 name = "ahash"
-version = "0.8.6"
+version = "0.8.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
+checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01"
 dependencies = [
  "cfg-if",
  "const-random",
- "getrandom 0.2.11",
+ "getrandom 0.2.12",
  "once_cell",
  "version_check",
  "zerocopy",
@@ -358,9 +358,9 @@ dependencies = [
 
 [[package]]
 name = "anstream"
-version = "0.6.4"
+version = "0.6.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44"
+checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5"
 dependencies = [
  "anstyle",
  "anstyle-parse",
@@ -378,37 +378,37 @@ checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
 
 [[package]]
 name = "anstyle-parse"
-version = "0.2.2"
+version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140"
+checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
 dependencies = [
  "utf8parse",
 ]
 
 [[package]]
 name = "anstyle-query"
-version = "1.0.0"
+version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
+checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
 dependencies = [
- "windows-sys 0.48.0",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
 name = "anstyle-wincon"
-version = "3.0.1"
+version = "3.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
+checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
 dependencies = [
  "anstyle",
- "windows-sys 0.48.0",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
 name = "anyhow"
-version = "1.0.75"
+version = "1.0.79"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
+checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
 
 [[package]]
 name = "assert-json-diff"
@@ -438,7 +438,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c"
 dependencies = [
  "concurrent-queue",
- "event-listener 4.0.0",
+ "event-listener 4.0.3",
  "event-listener-strategy",
  "futures-core",
  "pin-project-lite",
@@ -450,26 +450,26 @@ version = "1.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c"
 dependencies = [
- "async-lock 3.1.2",
+ "async-lock 3.3.0",
  "async-task",
  "concurrent-queue",
  "fastrand 2.0.1",
- "futures-lite 2.0.1",
+ "futures-lite 2.2.0",
  "slab",
 ]
 
 [[package]]
 name = "async-global-executor"
-version = "2.4.0"
+version = "2.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b4353121d5644cdf2beb5726ab752e79a8db1ebb52031770ec47db31d245526"
+checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c"
 dependencies = [
  "async-channel 2.1.1",
  "async-executor",
- "async-io 2.2.1",
- "async-lock 3.1.2",
+ "async-io 2.3.0",
+ "async-lock 3.3.0",
  "blocking",
- "futures-lite 2.0.1",
+ "futures-lite 2.2.0",
  "once_cell",
 ]
 
@@ -506,18 +506,18 @@ dependencies = [
 
 [[package]]
 name = "async-io"
-version = "2.2.1"
+version = "2.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d6d3b15875ba253d1110c740755e246537483f152fa334f91abd7fe84c88b3ff"
+checksum = "fb41eb19024a91746eba0773aa5e16036045bbf45733766661099e182ea6a744"
 dependencies = [
- "async-lock 3.1.2",
+ "async-lock 3.3.0",
  "cfg-if",
  "concurrent-queue",
  "futures-io",
- "futures-lite 2.0.1",
+ "futures-lite 2.2.0",
  "parking",
- "polling 3.3.1",
- "rustix 0.38.25",
+ "polling 3.3.2",
+ "rustix 0.38.30",
  "slab",
  "tracing",
  "windows-sys 0.52.0",
@@ -534,11 +534,11 @@ dependencies = [
 
 [[package]]
 name = "async-lock"
-version = "3.1.2"
+version = "3.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dea8b3453dd7cc96711834b75400d671b73e3656975fa68d9f277163b7f7e316"
+checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b"
 dependencies = [
- "event-listener 4.0.0",
+ "event-listener 4.0.3",
  "event-listener-strategy",
  "pin-project-lite",
 ]
@@ -557,19 +557,19 @@ dependencies = [
 
 [[package]]
 name = "async-task"
-version = "4.5.0"
+version = "4.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4eb2cdb97421e01129ccb49169d8279ed21e829929144f4a22a6e54ac549ca1"
+checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799"
 
 [[package]]
 name = "async-trait"
-version = "0.1.74"
+version = "0.1.77"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9"
+checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.48",
 ]
 
 [[package]]
@@ -616,9 +616,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
 
 [[package]]
 name = "base64"
-version = "0.21.5"
+version = "0.21.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
+checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
 
 [[package]]
 name = "bitflags"
@@ -628,9 +628,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
 [[package]]
 name = "bitflags"
-version = "2.4.1"
+version = "2.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
+checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
 
 [[package]]
 name = "block-buffer"
@@ -657,11 +657,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118"
 dependencies = [
  "async-channel 2.1.1",
- "async-lock 3.1.2",
+ "async-lock 3.3.0",
  "async-task",
  "fastrand 2.0.1",
  "futures-io",
- "futures-lite 2.0.1",
+ "futures-lite 2.2.0",
  "piper",
  "tracing",
 ]
@@ -759,7 +759,7 @@ checksum = "53fd06051bc34aa7ee753647dbd36506fa9f84026993d9871e65e0ee71ac3632"
 dependencies = [
  "async-trait",
  "fixedbitset",
- "getrandom 0.2.11",
+ "getrandom 0.2.12",
  "mini-moka",
  "once_cell",
  "parking_lot 0.12.1",
@@ -825,9 +825,9 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "4.4.8"
+version = "4.4.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64"
+checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c"
 dependencies = [
  "clap_builder",
  "clap_derive",
@@ -835,9 +835,9 @@ dependencies = [
 
 [[package]]
 name = "clap_builder"
-version = "4.4.8"
+version = "4.4.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc"
+checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7"
 dependencies = [
  "anstream",
  "anstyle",
@@ -854,7 +854,7 @@ dependencies = [
  "heck",
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.48",
 ]
 
 [[package]]
@@ -871,9 +871,9 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
 
 [[package]]
 name = "concurrent-queue"
-version = "2.3.0"
+version = "2.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400"
+checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363"
 dependencies = [
  "crossbeam-utils",
 ]
@@ -912,7 +912,7 @@ version = "0.1.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
 dependencies = [
- "getrandom 0.2.11",
+ "getrandom 0.2.12",
  "once_cell",
  "tiny-keccak",
 ]
@@ -930,7 +930,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
 dependencies = [
  "percent-encoding",
- "time 0.3.30",
+ "time 0.3.31",
  "version_check",
 ]
 
@@ -942,9 +942,9 @@ checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b"
 
 [[package]]
 name = "core-foundation"
-version = "0.9.3"
+version = "0.9.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
 dependencies = [
  "core-foundation-sys",
  "libc",
@@ -952,15 +952,15 @@ dependencies = [
 
 [[package]]
 name = "core-foundation-sys"
-version = "0.8.4"
+version = "0.8.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
+checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
 
 [[package]]
 name = "cpufeatures"
-version = "0.2.11"
+version = "0.2.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0"
+checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
 dependencies = [
  "libc",
 ]
@@ -991,32 +991,27 @@ dependencies = [
 
 [[package]]
 name = "crossbeam-channel"
-version = "0.5.8"
+version = "0.5.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
+checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b"
 dependencies = [
- "cfg-if",
  "crossbeam-utils",
 ]
 
 [[package]]
 name = "crossbeam-queue"
-version = "0.3.8"
+version = "0.3.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add"
+checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35"
 dependencies = [
- "cfg-if",
  "crossbeam-utils",
 ]
 
 [[package]]
 name = "crossbeam-utils"
-version = "0.8.16"
+version = "0.8.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
-dependencies = [
- "cfg-if",
-]
+checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
 
 [[package]]
 name = "crunchy"
@@ -1076,7 +1071,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
 dependencies = [
  "cfg-if",
- "hashbrown 0.14.2",
+ "hashbrown 0.14.3",
  "lock_api",
  "once_cell",
  "parking_lot_core 0.9.9",
@@ -1129,9 +1124,9 @@ dependencies = [
 
 [[package]]
 name = "deranged"
-version = "0.3.9"
+version = "0.3.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3"
+checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
 dependencies = [
  "powerfmt",
 ]
@@ -1142,7 +1137,16 @@ version = "0.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8"
 dependencies = [
- "derive_builder_macro",
+ "derive_builder_macro 0.12.0",
+]
+
+[[package]]
+name = "derive_builder"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "660047478bc508c0fde22c868991eec0c40a63e48d610befef466d48e2bee574"
+dependencies = [
+ "derive_builder_macro 0.13.0",
 ]
 
 [[package]]
@@ -1157,13 +1161,35 @@ dependencies = [
  "syn 1.0.109",
 ]
 
+[[package]]
+name = "derive_builder_core"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b217e6dd1011a54d12f3b920a411b5abd44b1716ecfe94f5f2f2f7b52e08ab7"
+dependencies = [
+ "darling",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
 [[package]]
 name = "derive_builder_macro"
 version = "0.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e"
 dependencies = [
- "derive_builder_core",
+ "derive_builder_core 0.12.0",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "derive_builder_macro"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5f77d7e20ac9153428f7ca14a88aba652adfc7a0ef0a06d654386310ef663b"
+dependencies = [
+ "derive_builder_core 0.13.0",
  "syn 1.0.109",
 ]
 
@@ -1234,11 +1260,11 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
 
 [[package]]
 name = "docker-compose-types"
-version = "0.7.0"
+version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "608ffe949fbffae4034bdde4bfa224238736ecc9e1a362997245c7282f49fa60"
+checksum = "6e366950e7785fe0e404901a2b4e5c09ebd6656767f0c2167e34c5068ce0cc2d"
 dependencies = [
- "derive_builder",
+ "derive_builder 0.13.0",
  "indexmap 2.1.0",
  "serde",
  "serde_yaml",
@@ -1276,12 +1302,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
 
 [[package]]
 name = "errno"
-version = "0.3.7"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8"
+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
 dependencies = [
  "libc",
- "windows-sys 0.48.0",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
@@ -1301,9 +1327,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
 
 [[package]]
 name = "event-listener"
-version = "4.0.0"
+version = "4.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "770d968249b5d99410d61f5bf89057f3199a077a04d087092f58e7d10692baae"
+checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e"
 dependencies = [
  "concurrent-queue",
  "parking",
@@ -1316,7 +1342,7 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3"
 dependencies = [
- "event-listener 4.0.0",
+ "event-listener 4.0.3",
  "pin-project-lite",
 ]
 
@@ -1419,9 +1445,9 @@ dependencies = [
 
 [[package]]
 name = "futures"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335"
+checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -1434,9 +1460,9 @@ dependencies = [
 
 [[package]]
 name = "futures-channel"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb"
+checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
 dependencies = [
  "futures-core",
  "futures-sink",
@@ -1444,15 +1470,15 @@ dependencies = [
 
 [[package]]
 name = "futures-core"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c"
+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
 
 [[package]]
 name = "futures-executor"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc"
+checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
 dependencies = [
  "futures-core",
  "futures-task",
@@ -1472,9 +1498,9 @@ dependencies = [
 
 [[package]]
 name = "futures-io"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa"
+checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
 
 [[package]]
 name = "futures-lite"
@@ -1493,40 +1519,39 @@ dependencies = [
 
 [[package]]
 name = "futures-lite"
-version = "2.0.1"
+version = "2.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3831c2651acb5177cbd83943f3d9c8912c5ad03c76afcc0e9511ba568ec5ebb"
+checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba"
 dependencies = [
  "fastrand 2.0.1",
  "futures-core",
  "futures-io",
- "memchr",
  "parking",
  "pin-project-lite",
 ]
 
 [[package]]
 name = "futures-macro"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb"
+checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.48",
 ]
 
 [[package]]
 name = "futures-sink"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817"
+checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
 
 [[package]]
 name = "futures-task"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2"
+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
 
 [[package]]
 name = "futures-timer"
@@ -1536,9 +1561,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
 
 [[package]]
 name = "futures-util"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104"
+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -1585,9 +1610,9 @@ dependencies = [
 
 [[package]]
 name = "getrandom"
-version = "0.2.11"
+version = "0.2.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
+checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
 dependencies = [
  "cfg-if",
  "js-sys",
@@ -1610,9 +1635,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
 
 [[package]]
 name = "h2"
-version = "0.3.22"
+version = "0.3.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178"
+checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9"
 dependencies = [
  "bytes",
  "fnv",
@@ -1647,11 +1672,11 @@ dependencies = [
 
 [[package]]
 name = "hashbrown"
-version = "0.14.2"
+version = "0.14.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156"
+checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
 dependencies = [
- "ahash 0.8.6",
+ "ahash 0.8.7",
  "allocator-api2",
 ]
 
@@ -1661,7 +1686,7 @@ version = "0.8.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
 dependencies = [
- "hashbrown 0.14.2",
+ "hashbrown 0.14.3",
 ]
 
 [[package]]
@@ -1675,9 +1700,9 @@ dependencies = [
 
 [[package]]
 name = "hermit-abi"
-version = "0.3.3"
+version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
+checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f"
 
 [[package]]
 name = "hex"
@@ -1687,9 +1712,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
 
 [[package]]
 name = "hkdf"
-version = "0.12.3"
+version = "0.12.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437"
+checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
 dependencies = [
  "hmac",
 ]
@@ -1716,9 +1741,9 @@ dependencies = [
 
 [[package]]
 name = "http-body"
-version = "0.4.5"
+version = "0.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
+checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
 dependencies = [
  "bytes",
  "http",
@@ -1760,9 +1785,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
 
 [[package]]
 name = "hyper"
-version = "0.14.27"
+version = "0.14.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468"
+checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80"
 dependencies = [
  "bytes",
  "futures-channel",
@@ -1775,7 +1800,7 @@ dependencies = [
  "httpdate",
  "itoa",
  "pin-project-lite",
- "socket2 0.4.10",
+ "socket2 0.5.5",
  "tokio",
  "tower-service",
  "tracing",
@@ -1797,9 +1822,9 @@ dependencies = [
 
 [[package]]
 name = "iana-time-zone"
-version = "0.1.58"
+version = "0.1.59"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20"
+checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539"
 dependencies = [
  "android_system_properties",
  "core-foundation-sys",
@@ -1852,7 +1877,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
 dependencies = [
  "equivalent",
- "hashbrown 0.14.2",
+ "hashbrown 0.14.3",
  "serde",
 ]
 
@@ -1909,18 +1934,18 @@ dependencies = [
 
 [[package]]
 name = "itertools"
-version = "0.11.0"
+version = "0.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
+checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0"
 dependencies = [
  "either",
 ]
 
 [[package]]
 name = "itoa"
-version = "1.0.9"
+version = "1.0.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
+checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
 
 [[package]]
 name = "jobserver"
@@ -1933,9 +1958,9 @@ dependencies = [
 
 [[package]]
 name = "js-sys"
-version = "0.3.65"
+version = "0.3.67"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8"
+checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1"
 dependencies = [
  "wasm-bindgen",
 ]
@@ -1988,9 +2013,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 
 [[package]]
 name = "libc"
-version = "0.2.150"
+version = "0.2.152"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
+checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
 
 [[package]]
 name = "libredox"
@@ -1998,7 +2023,7 @@ version = "0.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8"
 dependencies = [
- "bitflags 2.4.1",
+ "bitflags 2.4.2",
  "libc",
  "redox_syscall 0.4.1",
 ]
@@ -2017,9 +2042,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
 
 [[package]]
 name = "linux-raw-sys"
-version = "0.4.11"
+version = "0.4.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
+checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
 
 [[package]]
 name = "local-channel"
@@ -2075,9 +2100,9 @@ dependencies = [
 
 [[package]]
 name = "memchr"
-version = "2.6.4"
+version = "2.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
+checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
 
 [[package]]
 name = "mime"
@@ -2117,9 +2142,9 @@ dependencies = [
 
 [[package]]
 name = "mio"
-version = "0.8.9"
+version = "0.8.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0"
+checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
 dependencies = [
  "libc",
  "log",
@@ -2192,26 +2217,26 @@ dependencies = [
 
 [[package]]
 name = "object"
-version = "0.32.1"
+version = "0.32.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
+checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
 dependencies = [
  "memchr",
 ]
 
 [[package]]
 name = "once_cell"
-version = "1.18.0"
+version = "1.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
 
 [[package]]
 name = "openssl"
-version = "0.10.60"
+version = "0.10.63"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800"
+checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8"
 dependencies = [
- "bitflags 2.4.1",
+ "bitflags 2.4.2",
  "cfg-if",
  "foreign-types",
  "libc",
@@ -2228,7 +2253,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.48",
 ]
 
 [[package]]
@@ -2239,9 +2264,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
 
 [[package]]
 name = "openssl-sys"
-version = "0.9.96"
+version = "0.9.99"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f"
+checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae"
 dependencies = [
  "cc",
  "libc",
@@ -2274,7 +2299,7 @@ dependencies = [
  "cbc",
  "cipher",
  "des",
- "getrandom 0.2.11",
+ "getrandom 0.2.12",
  "hmac",
  "lazy_static",
  "rc2",
@@ -2356,9 +2381,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
 
 [[package]]
 name = "pest"
-version = "2.7.5"
+version = "2.7.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae9cee2a55a544be8b89dc6848072af97a20f2422603c10865be2a42b580fff5"
+checksum = "1f200d8d83c44a45b21764d1916299752ca035d15ecd46faca3e9a2a2bf6ad06"
 dependencies = [
  "memchr",
  "thiserror",
@@ -2367,9 +2392,9 @@ dependencies = [
 
 [[package]]
 name = "pest_derive"
-version = "2.7.5"
+version = "2.7.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81d78524685f5ef2a3b3bd1cafbc9fcabb036253d9b1463e726a91cd16e2dfc2"
+checksum = "bcd6ab1236bbdb3a49027e920e693192ebfe8913f6d60e294de57463a493cfde"
 dependencies = [
  "pest",
  "pest_generator",
@@ -2377,22 +2402,22 @@ dependencies = [
 
 [[package]]
 name = "pest_generator"
-version = "2.7.5"
+version = "2.7.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68bd1206e71118b5356dae5ddc61c8b11e28b09ef6a31acbd15ea48a28e0c227"
+checksum = "2a31940305ffc96863a735bef7c7994a00b325a7138fdbc5bda0f1a0476d3275"
 dependencies = [
  "pest",
  "pest_meta",
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.48",
 ]
 
 [[package]]
 name = "pest_meta"
-version = "2.7.5"
+version = "2.7.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7c747191d4ad9e4a4ab9c8798f1e82a39affe7ef9648390b7e5548d18e099de6"
+checksum = "a7ff62f5259e53b78d1af898941cdcdccfae7385cf7d793a6e55de5d05bb4b7d"
 dependencies = [
  "once_cell",
  "pest",
@@ -2411,22 +2436,22 @@ dependencies = [
 
 [[package]]
 name = "pin-project"
-version = "1.1.3"
+version = "1.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422"
+checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0"
 dependencies = [
  "pin-project-internal",
 ]
 
 [[package]]
 name = "pin-project-internal"
-version = "1.1.3"
+version = "1.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
+checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.48",
 ]
 
 [[package]]
@@ -2466,9 +2491,9 @@ dependencies = [
 
 [[package]]
 name = "pkg-config"
-version = "0.3.27"
+version = "0.3.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
+checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb"
 
 [[package]]
 name = "polling"
@@ -2488,14 +2513,14 @@ dependencies = [
 
 [[package]]
 name = "polling"
-version = "3.3.1"
+version = "3.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf63fa624ab313c11656b4cda960bfc46c410187ad493c41f6ba2d8c1e991c9e"
+checksum = "545c980a3880efd47b2e262f6a4bb6daad6555cf3367aa9c4e52895f69537a41"
 dependencies = [
  "cfg-if",
  "concurrent-queue",
  "pin-project-lite",
- "rustix 0.38.25",
+ "rustix 0.38.30",
  "tracing",
  "windows-sys 0.52.0",
 ]
@@ -2538,29 +2563,29 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.70"
+version = "1.0.78"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
+checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "pulldown-cmark"
-version = "0.9.3"
+version = "0.9.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998"
+checksum = "b16a41fe73d9f20da4dae1440a2c4f23db602db9b4699b9b694f007c0a84f67d"
 dependencies = [
- "bitflags 1.3.2",
+ "bitflags 2.4.2",
  "memchr",
  "unicase",
 ]
 
 [[package]]
 name = "quote"
-version = "1.0.33"
+version = "1.0.35"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
 dependencies = [
  "proc-macro2",
 ]
@@ -2624,7 +2649,7 @@ version = "0.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
 dependencies = [
- "getrandom 0.2.11",
+ "getrandom 0.2.12",
 ]
 
 [[package]]
@@ -2680,20 +2705,20 @@ version = "0.4.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4"
 dependencies = [
- "getrandom 0.2.11",
+ "getrandom 0.2.12",
  "libredox",
  "thiserror",
 ]
 
 [[package]]
 name = "regex"
-version = "1.10.2"
+version = "1.10.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
+checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
 dependencies = [
  "aho-corasick",
  "memchr",
- "regex-automata 0.4.3",
+ "regex-automata 0.4.5",
  "regex-syntax 0.8.2",
 ]
 
@@ -2708,9 +2733,9 @@ dependencies = [
 
 [[package]]
 name = "regex-automata"
-version = "0.4.3"
+version = "0.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
+checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -2731,11 +2756,11 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
 
 [[package]]
 name = "reqwest"
-version = "0.11.22"
+version = "0.11.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b"
+checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41"
 dependencies = [
- "base64 0.21.5",
+ "base64 0.21.7",
  "bytes",
  "encoding_rs",
  "futures-core",
@@ -2779,8 +2804,8 @@ version = "1.16.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e3625f343d89990133d013e39c46e350915178cf94f1bec9f49b0cbef98a3e3c"
 dependencies = [
- "ahash 0.8.6",
- "bitflags 2.4.1",
+ "ahash 0.8.7",
+ "bitflags 2.4.2",
  "instant",
  "num-traits",
  "once_cell",
@@ -2798,7 +2823,7 @@ checksum = "853977598f084a492323fe2f7896b4100a86284ee8473612de60021ea341310f"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.48",
 ]
 
 [[package]]
@@ -2818,12 +2843,12 @@ dependencies = [
 
 [[package]]
 name = "ring"
-version = "0.17.5"
+version = "0.17.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b"
+checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74"
 dependencies = [
  "cc",
- "getrandom 0.2.11",
+ "getrandom 0.2.12",
  "libc",
  "spin 0.9.8",
  "untrusted 0.9.0",
@@ -2892,15 +2917,15 @@ dependencies = [
 
 [[package]]
 name = "rustix"
-version = "0.38.25"
+version = "0.38.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e"
+checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca"
 dependencies = [
- "bitflags 2.4.1",
+ "bitflags 2.4.2",
  "errno",
  "libc",
- "linux-raw-sys 0.4.11",
- "windows-sys 0.48.0",
+ "linux-raw-sys 0.4.13",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
@@ -2917,12 +2942,12 @@ dependencies = [
 
 [[package]]
 name = "rustls"
-version = "0.21.9"
+version = "0.21.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9"
+checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba"
 dependencies = [
  "log",
- "ring 0.17.5",
+ "ring 0.17.7",
  "rustls-webpki",
  "sct",
 ]
@@ -2934,7 +2959,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "25da151615461c7347114b1ad1a7458b4cdebc69cb220cd140cd5cb324b1dd37"
 dependencies = [
  "log",
- "rustls 0.21.9",
+ "rustls 0.21.10",
  "rustls-native-certs",
  "rustls-webpki",
 ]
@@ -2957,7 +2982,7 @@ version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
 dependencies = [
- "base64 0.21.5",
+ "base64 0.21.7",
 ]
 
 [[package]]
@@ -2966,15 +2991,15 @@ version = "0.101.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
 dependencies = [
- "ring 0.17.5",
+ "ring 0.17.7",
  "untrusted 0.9.0",
 ]
 
 [[package]]
 name = "ryu"
-version = "1.0.15"
+version = "1.0.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
+checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
 
 [[package]]
 name = "same-file"
@@ -2987,11 +3012,11 @@ dependencies = [
 
 [[package]]
 name = "schannel"
-version = "0.1.22"
+version = "0.1.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88"
+checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534"
 dependencies = [
- "windows-sys 0.48.0",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
@@ -3006,7 +3031,7 @@ version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
 dependencies = [
- "ring 0.17.5",
+ "ring 0.17.7",
  "untrusted 0.9.0",
 ]
 
@@ -3035,38 +3060,38 @@ dependencies = [
 
 [[package]]
 name = "semver"
-version = "1.0.20"
+version = "1.0.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
+checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
 dependencies = [
  "serde",
 ]
 
 [[package]]
 name = "serde"
-version = "1.0.193"
+version = "1.0.196"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
+checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.193"
+version = "1.0.196"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
+checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.48",
 ]
 
 [[package]]
 name = "serde_json"
-version = "1.0.108"
+version = "1.0.112"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
+checksum = "4d1bd37ce2324cf3bf85e5a25f96eb4baf0d5aa6eba43e7ae8958870c4ec48ed"
 dependencies = [
  "itoa",
  "ryu",
@@ -3075,9 +3100,9 @@ dependencies = [
 
 [[package]]
 name = "serde_path_to_error"
-version = "0.1.14"
+version = "0.1.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335"
+checksum = "ebd154a240de39fdebcf5775d2675c204d7c13cf39a4c697be6493c8e734337c"
 dependencies = [
  "itoa",
  "serde",
@@ -3152,9 +3177,9 @@ dependencies = [
 
 [[package]]
 name = "serde_yaml"
-version = "0.9.27"
+version = "0.9.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c"
+checksum = "b1bf28c79a99f70ee1f1d83d10c875d2e70618417fda01ad1785e027579d9d38"
 dependencies = [
  "indexmap 2.1.0",
  "itoa",
@@ -3229,9 +3254,9 @@ dependencies = [
 
 [[package]]
 name = "smallvec"
-version = "1.11.2"
+version = "1.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
+checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
 dependencies = [
  "serde",
 ]
@@ -3285,11 +3310,11 @@ dependencies = [
 
 [[package]]
 name = "sqlformat"
-version = "0.2.2"
+version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6b7b278788e7be4d0d29c0f39497a0eef3fba6bbc8e70d8bf7fde46edeaa9e85"
+checksum = "ce81b7bd7c4493975347ef60d8c7e8b742d4694f4c49f93e0a12ea263938176c"
 dependencies = [
- "itertools 0.11.0",
+ "itertools 0.12.0",
  "nom",
  "unicode_categories",
 ]
@@ -3405,7 +3430,7 @@ dependencies = [
  "clap",
  "config",
  "deadpool-lapin",
- "derive_builder",
+ "derive_builder 0.12.0",
  "docker-compose-types",
  "futures",
  "futures-lite 1.13.0",
@@ -3479,9 +3504,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.39"
+version = "2.0.48"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
+checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -3529,35 +3554,35 @@ dependencies = [
 
 [[package]]
 name = "tempfile"
-version = "3.8.1"
+version = "3.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
+checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa"
 dependencies = [
  "cfg-if",
  "fastrand 2.0.1",
  "redox_syscall 0.4.1",
- "rustix 0.38.25",
- "windows-sys 0.48.0",
+ "rustix 0.38.30",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
 name = "thiserror"
-version = "1.0.50"
+version = "1.0.56"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
+checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.50"
+version = "1.0.56"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
+checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.48",
 ]
 
 [[package]]
@@ -3583,9 +3608,9 @@ dependencies = [
 
 [[package]]
 name = "time"
-version = "0.3.30"
+version = "0.3.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
+checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e"
 dependencies = [
  "deranged",
  "itoa",
@@ -3603,9 +3628,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
 
 [[package]]
 name = "time-macros"
-version = "0.2.15"
+version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20"
+checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f"
 dependencies = [
  "time-core",
 ]
@@ -3636,9 +3661,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
 
 [[package]]
 name = "tokio"
-version = "1.34.0"
+version = "1.35.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9"
+checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
 dependencies = [
  "backtrace",
  "bytes",
@@ -3672,7 +3697,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.48",
 ]
 
 [[package]]
@@ -3769,7 +3794,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.48",
 ]
 
 [[package]]
@@ -3778,12 +3803,12 @@ version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b5c266b9ac83dedf0e0385ad78514949e6d89491269e7065bee51d2bb8ec7373"
 dependencies = [
- "ahash 0.8.6",
+ "ahash 0.8.7",
  "gethostname",
  "log",
  "serde",
  "serde_json",
- "time 0.3.30",
+ "time 0.3.31",
  "tracing",
  "tracing-core",
  "tracing-log 0.1.4",
@@ -3848,9 +3873,9 @@ checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3"
 
 [[package]]
 name = "try-lock"
-version = "0.2.4"
+version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
 
 [[package]]
 name = "typenum"
@@ -3875,9 +3900,9 @@ dependencies = [
 
 [[package]]
 name = "unicode-bidi"
-version = "0.3.13"
+version = "0.3.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
+checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
 
 [[package]]
 name = "unicode-ident"
@@ -3908,9 +3933,9 @@ checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
 
 [[package]]
 name = "unsafe-libyaml"
-version = "0.2.9"
+version = "0.2.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa"
+checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b"
 
 [[package]]
 name = "untrusted"
@@ -3944,11 +3969,11 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
 
 [[package]]
 name = "uuid"
-version = "1.6.1"
+version = "1.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560"
+checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a"
 dependencies = [
- "getrandom 0.2.11",
+ "getrandom 0.2.12",
  "serde",
 ]
 
@@ -4015,9 +4040,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.88"
+version = "0.2.90"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce"
+checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406"
 dependencies = [
  "cfg-if",
  "wasm-bindgen-macro",
@@ -4025,24 +4050,24 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.88"
+version = "0.2.90"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217"
+checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd"
 dependencies = [
  "bumpalo",
  "log",
  "once_cell",
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.48",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-futures"
-version = "0.4.38"
+version = "0.4.40"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02"
+checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461"
 dependencies = [
  "cfg-if",
  "js-sys",
@@ -4052,9 +4077,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.88"
+version = "0.2.90"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2"
+checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -4062,28 +4087,28 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.88"
+version = "0.2.90"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907"
+checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.48",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.88"
+version = "0.2.90"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b"
+checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b"
 
 [[package]]
 name = "web-sys"
-version = "0.3.65"
+version = "0.3.67"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85"
+checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
@@ -4095,7 +4120,7 @@ version = "0.22.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53"
 dependencies = [
- "ring 0.17.5",
+ "ring 0.17.7",
  "untrusted 0.9.0",
 ]
 
@@ -4151,11 +4176,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
 [[package]]
 name = "windows-core"
-version = "0.51.1"
+version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
 dependencies = [
- "windows-targets 0.48.5",
+ "windows-targets 0.52.0",
 ]
 
 [[package]]
@@ -4308,7 +4333,7 @@ checksum = "13a3a53eaf34f390dd30d7b1b078287dd05df2aa2e21a589ccb80f5c7253c2e9"
 dependencies = [
  "assert-json-diff",
  "async-trait",
- "base64 0.21.5",
+ "base64 0.21.7",
  "deadpool 0.9.5",
  "futures",
  "futures-timer",
@@ -4339,40 +4364,39 @@ checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
 
 [[package]]
 name = "zerocopy"
-version = "0.7.26"
+version = "0.7.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e97e415490559a91254a2979b4829267a57d2fcd741a98eee8b722fb57289aa0"
+checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
 dependencies = [
  "zerocopy-derive",
 ]
 
 [[package]]
 name = "zerocopy-derive"
-version = "0.7.26"
+version = "0.7.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd7e48ccf166952882ca8bd778a43502c64f33bf94c12ebe2a7f08e5a0f6689f"
+checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.48",
 ]
 
 [[package]]
 name = "zstd"
-version = "0.12.4"
+version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a27595e173641171fc74a1232b7b1c7a7cb6e18222c11e9dfb9888fa424c53c"
+checksum = "bffb3309596d527cfcba7dfc6ed6052f1d39dfbd7c867aa2e865e4a449c10110"
 dependencies = [
  "zstd-safe",
 ]
 
 [[package]]
 name = "zstd-safe"
-version = "6.0.6"
+version = "7.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee98ffd0b48ee95e6c5168188e44a54550b1564d9d530ee21d5f0eaed1069581"
+checksum = "43747c7422e2924c11144d5229878b98180ef8b06cca4ab5af37afc8a8d8ea3e"
 dependencies = [
- "libc",
  "zstd-sys",
 ]
 
diff --git a/Cargo.toml b/Cargo.toml
index abf15fc8..33c4e358 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -55,7 +55,8 @@ clap = { version = "4.4.8", features = ["derive"] }
 serde_path_to_error = "0.1.14"
 deadpool-lapin = "0.11.0"
 docker-compose-types = "0.7.0"
-actix-casbin-auth = { git = "https://github.com/casbin-rs/actix-casbin-auth.git"}
+#actix-casbin-auth = { git = "https://github.com/casbin-rs/actix-casbin-auth.git"}
+actix-casbin-auth = { git = "https://github.com/smart--petea/actix-casbin-auth.git", branch = "dirty-master"}
 
 [dependencies.sqlx]
 version = "0.6.3"
diff --git a/rbac/rbac_with_pattern_model.conf b/rbac/rbac_with_pattern_model.conf
index 44507a61..5d047154 100644
--- a/rbac/rbac_with_pattern_model.conf
+++ b/rbac/rbac_with_pattern_model.conf
@@ -1,15 +1,14 @@
 [request_definition]
-r = sub, obj, act
+r = sub, dom, obj, act
 
 [policy_definition]
-p = sub, obj, act
+p = sub, dom, obj, act
 
 [role_definition]
-g = _, _
-g2 = _, _
+g = _, _, _
 
 [policy_effect]
 e = some(where (p.eft == allow))
 
 [matchers]
-m = g(r.sub, p.sub) && g2(r.obj, p.obj) && regexMatch(r.act, p.act)
+m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && regexMatch(r.act, p.act)
diff --git a/rbac/rbac_with_pattern_policy.csv b/rbac/rbac_with_pattern_policy.csv
index ea5b1a61..7526184c 100644
--- a/rbac/rbac_with_pattern_policy.csv
+++ b/rbac/rbac_with_pattern_policy.csv
@@ -1,10 +1,6 @@
-p, petru, /testcasbin, GET
-p, alice, /pen/1, GET
-p, alice, /pen2/1, GET
-p, book_admin, book_group, GET
-p, pen_admin, pen_group, GET
-
-g, alice, book_admin
-g, bob, pen_admin
-g2, /book/:id, book_group
-g2, /pen/:id, pen_group
+p, admin, domain1, /pen/1, GET
+p, admin, domain1, /pen/2, GET
+p, admin, domain2, /book/1, GET
+p, admin, domain2, /book/2, GET
+g, alice, admin, domain1
+g, bob, admin, domain2
diff --git a/src/middleware/trydirect.rs b/src/middleware/trydirect.rs
index 33c54a73..0afea4d8 100644
--- a/src/middleware/trydirect.rs
+++ b/src/middleware/trydirect.rs
@@ -22,7 +22,7 @@ pub async fn bearer_guard( req: ServiceRequest, credentials: BearerAuth) -> Resu
 
     let vals = actix_casbin_auth::CasbinVals { //todo
         subject: String::from("alice"),
-        domain: Some("/pen/1".to_string()),
+        domain: Some("domain1".to_string()),
     };
     let result = req.extensions_mut().insert(vals); //todo
     tracing::error!("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA {}", result.is_some()); //todo
diff --git a/src/startup.rs b/src/startup.rs
index f00d0d83..91a01133 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -33,7 +33,7 @@ pub async fn run(
         .unwrap();
     let a = FileAdapter::new("rbac/rbac_with_pattern_policy.csv");  //You can also use diesel-adapter or sqlx-adapter
 
-    let casbin_middleware = CasbinService::new(m, a).await.unwrap(); //todo
+    let mut casbin_middleware = CasbinService::new(m, a).await.unwrap(); //todo
 
     casbin_middleware
         .write()
@@ -60,7 +60,6 @@ pub async fn run(
             )
             .service(
                 web::scope("/test")
-                    .wrap(casbin_middleware.clone())
                     .wrap(crate::middleware::client::Guard::new())
                     .wrap(Cors::permissive())
                     .service(crate::routes::test::deploy::handler),

From a7b5f1acd04c99d0c7eb2492e1440538006c31d0 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sun, 28 Jan 2024 10:05:44 +0200
Subject: [PATCH 155/284] issue-auth access_manager

---
 src/middleware/access_control.rs |  5 -----
 src/middleware/access_manager.rs | 25 +++++++++++++++++++++++
 src/middleware/mod.rs            |  2 +-
 src/startup.rs                   | 34 ++++++++------------------------
 4 files changed, 34 insertions(+), 32 deletions(-)
 delete mode 100644 src/middleware/access_control.rs
 create mode 100644 src/middleware/access_manager.rs

diff --git a/src/middleware/access_control.rs b/src/middleware/access_control.rs
deleted file mode 100644
index 80e48813..00000000
--- a/src/middleware/access_control.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-pub struct AccessControl{}
-
-impl AccessControl {
-
-}
diff --git a/src/middleware/access_manager.rs b/src/middleware/access_manager.rs
new file mode 100644
index 00000000..04c2b607
--- /dev/null
+++ b/src/middleware/access_manager.rs
@@ -0,0 +1,25 @@
+use actix_casbin_auth::casbin::{DefaultModel, FileAdapter};//, Result};
+use actix_casbin_auth::CasbinService;
+use actix_casbin_auth::casbin::function_map::key_match2;
+use actix_casbin_auth::casbin::CoreApi;
+use std::io::{Error, ErrorKind};
+
+pub async fn try_new() -> Result<CasbinService, Error> {
+    let m = DefaultModel::from_file("rbac/rbac_with_pattern_model.conf")
+        .await
+        .map_err(|err| Error::new(ErrorKind::Other, format!("{err:?}")))?;
+    let a = FileAdapter::new("rbac/rbac_with_pattern_policy.csv");  
+
+    let mut casbin_service = CasbinService::new(m, a)
+        .await
+        .map_err(|err| Error::new(ErrorKind::Other, format!("{err:?}")))?;
+
+    casbin_service
+        .write()
+        .await
+        .get_role_manager()
+        .write()
+        .matching_fn(Some(key_match2), None);
+
+    Ok(casbin_service)
+}
diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs
index 66bc6dcf..59debf10 100644
--- a/src/middleware/mod.rs
+++ b/src/middleware/mod.rs
@@ -1,3 +1,3 @@
 pub mod client;
 pub mod trydirect;
-pub mod access_control;
+pub mod access_manager;
diff --git a/src/startup.rs b/src/startup.rs
index 91a01133..8c872f31 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -6,16 +6,12 @@ use actix_web::{
     web::{self},
     App, HttpServer,
 };
+use crate::middleware;
 use actix_web_httpauth::middleware::HttpAuthentication;
 use sqlx::{Pool, Postgres};
 use std::net::TcpListener;
 use tracing_actix_web::TracingLogger;
 
-use actix_casbin_auth::casbin::{DefaultModel, FileAdapter};//, Result};
-use actix_casbin_auth::CasbinService;
-use actix_casbin_auth::casbin::function_map::key_match2;
-use actix_casbin_auth::casbin::CoreApi;
-
 pub async fn run(
     listener: TcpListener,
     pg_pool: Pool<Postgres>,
@@ -27,21 +23,7 @@ pub async fn run(
     let mq_manager = helpers::MqManager::try_new(settings.amqp.connection_string())?;
     let mq_manager = web::Data::new(mq_manager);
 
-    //casbin
-     let m = DefaultModel::from_file("rbac/rbac_with_pattern_model.conf")
-        .await
-        .unwrap();
-    let a = FileAdapter::new("rbac/rbac_with_pattern_policy.csv");  //You can also use diesel-adapter or sqlx-adapter
-
-    let mut casbin_middleware = CasbinService::new(m, a).await.unwrap(); //todo
-
-    casbin_middleware
-        .write()
-        .await
-        .get_role_manager()
-        .write()
-        //.unwrap()
-        .matching_fn(Some(key_match2), None);
+    let access_control_manager = middleware::access_manager::try_new().await?;
 
     let server = HttpServer::new(move || {
         App::new()
@@ -50,7 +32,7 @@ pub async fn run(
             .service(
                 web::scope("/client")
                     .wrap(HttpAuthentication::bearer(
-                        crate::middleware::trydirect::bearer_guard,
+                        middleware::trydirect::bearer_guard,
                     ))
                     .wrap(Cors::permissive())
                     .service(crate::routes::client::add_handler)
@@ -60,15 +42,15 @@ pub async fn run(
             )
             .service(
                 web::scope("/test")
-                    .wrap(crate::middleware::client::Guard::new())
+                    .wrap(middleware::client::Guard::new())
                     .wrap(Cors::permissive())
                     .service(crate::routes::test::deploy::handler),
             )
             .service(
                 web::scope("/pen/1")
-                    .wrap(casbin_middleware.clone()) //todo
+                    .wrap(access_control_manager.clone()) 
                     .wrap(HttpAuthentication::bearer(
-                        crate::middleware::trydirect::bearer_guard,
+                        middleware::trydirect::bearer_guard,
                     ))
                     .wrap(Cors::permissive())
                     .service(crate::routes::test::casbin::handler),
@@ -76,7 +58,7 @@ pub async fn run(
             .service(
                 web::scope("/rating")
                     .wrap(HttpAuthentication::bearer(
-                        crate::middleware::trydirect::bearer_guard,
+                        middleware::trydirect::bearer_guard,
                     ))
                     .wrap(Cors::permissive())
                     .service(crate::routes::rating::add_handler)
@@ -86,7 +68,7 @@ pub async fn run(
             .service(
                 web::scope("/stack")
                     .wrap(HttpAuthentication::bearer(
-                        crate::middleware::trydirect::bearer_guard,
+                        middleware::trydirect::bearer_guard,
                     ))
                     .wrap(Cors::permissive())
                     .service(crate::routes::stack::deploy::add)

From 94fbd7b02f1a62de30f84884802b70939a50a73a Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sun, 28 Jan 2024 20:00:01 +0200
Subject: [PATCH 156/284] issue-auth casbin_rule migration

---
 Cargo.toml                                           |  1 +
 .../20230905145525_creating_stack_tables.down.sql    |  1 -
 .../20230917162549_creating_test_product.down.sql    |  2 +-
 .../20230917162549_creating_test_product.up.sql      |  2 +-
 migrations/20231028161917_client.up.sql              |  2 +-
 migrations/20240128174529_casbin_rule.down.sql       |  2 ++
 migrations/20240128174529_casbin_rule.up.sql         | 12 ++++++++++++
 7 files changed, 18 insertions(+), 4 deletions(-)
 create mode 100644 migrations/20240128174529_casbin_rule.down.sql
 create mode 100644 migrations/20240128174529_casbin_rule.up.sql

diff --git a/Cargo.toml b/Cargo.toml
index 33c4e358..894ba669 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -44,6 +44,7 @@ tokio-stream = "0.1.14"
 actix-http = "3.4.0"
 hmac = "0.12.1"
 sha2 = "0.10.8"
+sqlx-adapter = { version = "0.4.2", default-features = false, features = ["postgres", "runtime-tokio-native-tls"]}
 
 # dctypes
 derive_builder = "0.12.0"
diff --git a/migrations/20230905145525_creating_stack_tables.down.sql b/migrations/20230905145525_creating_stack_tables.down.sql
index 203a95a2..2f2a6e48 100644
--- a/migrations/20230905145525_creating_stack_tables.down.sql
+++ b/migrations/20230905145525_creating_stack_tables.down.sql
@@ -1,3 +1,2 @@
 -- Add down migration script here
-
 DROP TABLE user_stack;
diff --git a/migrations/20230917162549_creating_test_product.down.sql b/migrations/20230917162549_creating_test_product.down.sql
index f9f63393..eafea959 100644
--- a/migrations/20230917162549_creating_test_product.down.sql
+++ b/migrations/20230917162549_creating_test_product.down.sql
@@ -1 +1 @@
-delete from product where id=1;
+DELETE FROM product WHERE id=1;
diff --git a/migrations/20230917162549_creating_test_product.up.sql b/migrations/20230917162549_creating_test_product.up.sql
index 7a1d8d62..9aae3c5f 100644
--- a/migrations/20230917162549_creating_test_product.up.sql
+++ b/migrations/20230917162549_creating_test_product.up.sql
@@ -1 +1 @@
-INSERT INTO public.product (id, obj_id, obj_type, created_at, updated_at) VALUES(1, 1, 'Application', '2023-09-17 10:30:02.579', '2023-09-17 10:30:02.579');
\ No newline at end of file
+INSERT INTO product (id, obj_id, obj_type, created_at, updated_at) VALUES(1, 1, 'Application', '2023-09-17 10:30:02.579', '2023-09-17 10:30:02.579');
diff --git a/migrations/20231028161917_client.up.sql b/migrations/20231028161917_client.up.sql
index fcb9065b..e0470c3b 100644
--- a/migrations/20231028161917_client.up.sql
+++ b/migrations/20231028161917_client.up.sql
@@ -1,5 +1,5 @@
 -- Add up migration script here
-CREATE TABLE public.client (
+CREATE TABLE client (
   id serial4 NOT NULL,
   user_id varchar(50) NOT NULL,
   secret varchar(255),
diff --git a/migrations/20240128174529_casbin_rule.down.sql b/migrations/20240128174529_casbin_rule.down.sql
new file mode 100644
index 00000000..ef4c417d
--- /dev/null
+++ b/migrations/20240128174529_casbin_rule.down.sql
@@ -0,0 +1,2 @@
+-- Add down migration script here
+DROP TABLE casbin_rule;
diff --git a/migrations/20240128174529_casbin_rule.up.sql b/migrations/20240128174529_casbin_rule.up.sql
new file mode 100644
index 00000000..15b99142
--- /dev/null
+++ b/migrations/20240128174529_casbin_rule.up.sql
@@ -0,0 +1,12 @@
+-- Add up migration script here
+CREATE TABLE casbin_rule (
+    id SERIAL PRIMARY KEY,
+    ptype VARCHAR NOT NULL,
+    v0 VARCHAR NOT NULL,
+    v1 VARCHAR NOT NULL,
+    v2 VARCHAR NOT NULL,
+    v3 VARCHAR NOT NULL,
+    v4 VARCHAR NOT NULL,
+    v5 VARCHAR NOT NULL,
+    CONSTRAINT unique_key_sqlx_adapter UNIQUE(ptype, v0, v1, v2, v3, v4, v5)
+)

From 7348383e97e2cc765a4f338067054fb5a7127b10 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Mon, 29 Jan 2024 17:51:54 +0200
Subject: [PATCH 157/284] issue-auth casbin adapter

---
 Cargo.lock                       | 50 ++++++++++++++++++++++----------
 Cargo.toml                       |  3 +-
 src/middleware/access_manager.rs | 19 ++++++++----
 src/startup.rs                   |  2 +-
 4 files changed, 50 insertions(+), 24 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index c4ceaf8a..f844c9c0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -466,7 +466,7 @@ checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c"
 dependencies = [
  "async-channel 2.1.1",
  "async-executor",
- "async-io 2.3.0",
+ "async-io 2.3.1",
  "async-lock 3.3.0",
  "blocking",
  "futures-lite 2.2.0",
@@ -506,9 +506,9 @@ dependencies = [
 
 [[package]]
 name = "async-io"
-version = "2.3.0"
+version = "2.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb41eb19024a91746eba0773aa5e16036045bbf45733766661099e182ea6a744"
+checksum = "8f97ab0c5b00a7cdbe5a371b9a782ee7be1316095885c8a4ea1daf490eb0ef65"
 dependencies = [
  "async-lock 3.3.0",
  "cfg-if",
@@ -1265,11 +1265,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6e366950e7785fe0e404901a2b4e5c09ebd6656767f0c2167e34c5068ce0cc2d"
 dependencies = [
  "derive_builder 0.13.0",
- "indexmap 2.1.0",
+ "indexmap 2.2.1",
  "serde",
  "serde_yaml",
 ]
 
+[[package]]
+name = "dotenv"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
+
 [[package]]
 name = "dotenvy"
 version = "0.15.7"
@@ -1645,7 +1651,7 @@ dependencies = [
  "futures-sink",
  "futures-util",
  "http",
- "indexmap 2.1.0",
+ "indexmap 2.2.1",
  "slab",
  "tokio",
  "tokio-util",
@@ -1872,9 +1878,9 @@ dependencies = [
 
 [[package]]
 name = "indexmap"
-version = "2.1.0"
+version = "2.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
+checksum = "433de089bd45971eecf4668ee0ee8f4cec17db4f8bd8f7bc3197a6ce37aa7d9b"
 dependencies = [
  "equivalent",
  "hashbrown 0.14.3",
@@ -2431,7 +2437,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
 dependencies = [
  "fixedbitset",
- "indexmap 2.1.0",
+ "indexmap 2.2.1",
 ]
 
 [[package]]
@@ -2572,9 +2578,9 @@ dependencies = [
 
 [[package]]
 name = "pulldown-cmark"
-version = "0.9.4"
+version = "0.9.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b16a41fe73d9f20da4dae1440a2c4f23db602db9b4699b9b694f007c0a84f67d"
+checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b"
 dependencies = [
  "bitflags 2.4.2",
  "memchr",
@@ -3089,9 +3095,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.112"
+version = "1.0.113"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d1bd37ce2324cf3bf85e5a25f96eb4baf0d5aa6eba43e7ae8958870c4ec48ed"
+checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
 dependencies = [
  "itoa",
  "ryu",
@@ -3177,11 +3183,11 @@ dependencies = [
 
 [[package]]
 name = "serde_yaml"
-version = "0.9.30"
+version = "0.9.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1bf28c79a99f70ee1f1d83d10c875d2e70618417fda01ad1785e027579d9d38"
+checksum = "adf8a49373e98a4c5f0ceb5d05aa7c648d75f63774981ed95b7c7443bbd50c6e"
 dependencies = [
- "indexmap 2.1.0",
+ "indexmap 2.2.1",
  "itoa",
  "ryu",
  "serde",
@@ -3329,6 +3335,17 @@ dependencies = [
  "sqlx-macros",
 ]
 
+[[package]]
+name = "sqlx-adapter"
+version = "0.4.2"
+source = "git+https://github.com/smart--petea/sqlx-adapter.git?branch=dirty-master#5f7cabbde494098a1b460f26c4b74fcbcf2d5857"
+dependencies = [
+ "async-trait",
+ "casbin",
+ "dotenv",
+ "sqlx",
+]
+
 [[package]]
 name = "sqlx-core"
 version = "0.6.3"
@@ -3437,7 +3454,7 @@ dependencies = [
  "futures-util",
  "glob",
  "hmac",
- "indexmap 2.1.0",
+ "indexmap 2.2.1",
  "lapin",
  "rand 0.8.5",
  "regex",
@@ -3450,6 +3467,7 @@ dependencies = [
  "serde_yaml",
  "sha2",
  "sqlx",
+ "sqlx-adapter",
  "thiserror",
  "tokio",
  "tokio-stream",
diff --git a/Cargo.toml b/Cargo.toml
index 894ba669..89420989 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -44,7 +44,8 @@ tokio-stream = "0.1.14"
 actix-http = "3.4.0"
 hmac = "0.12.1"
 sha2 = "0.10.8"
-sqlx-adapter = { version = "0.4.2", default-features = false, features = ["postgres", "runtime-tokio-native-tls"]}
+#sqlx-adapter = { version = "0.4.2", default-features = false, features = ["postgres", "runtime-tokio-native-tls"]}
+sqlx-adapter = { git="https://github.com/smart--petea/sqlx-adapter.git", branch="dirty-master", default-features = false, features = ["postgres"]}
 
 # dctypes
 derive_builder = "0.12.0"
diff --git a/src/middleware/access_manager.rs b/src/middleware/access_manager.rs
index 04c2b607..82301cd1 100644
--- a/src/middleware/access_manager.rs
+++ b/src/middleware/access_manager.rs
@@ -1,14 +1,21 @@
-use actix_casbin_auth::casbin::{DefaultModel, FileAdapter};//, Result};
-use actix_casbin_auth::CasbinService;
-use actix_casbin_auth::casbin::function_map::key_match2;
-use actix_casbin_auth::casbin::CoreApi;
+use actix_casbin_auth::{
+    CasbinService,
+    casbin::{
+        DefaultModel,
+        CoreApi,
+        function_map::key_match2
+    }
+};
 use std::io::{Error, ErrorKind};
+use sqlx_adapter::SqlxAdapter;
 
-pub async fn try_new() -> Result<CasbinService, Error> {
+pub async fn try_new(db_connection_address: String) -> Result<CasbinService, Error> {
     let m = DefaultModel::from_file("rbac/rbac_with_pattern_model.conf")
         .await
         .map_err(|err| Error::new(ErrorKind::Other, format!("{err:?}")))?;
-    let a = FileAdapter::new("rbac/rbac_with_pattern_policy.csv");  
+    let a = SqlxAdapter::new(db_connection_address, 8)
+        .await
+        .map_err(|err| Error::new(ErrorKind::Other, format!("{err:?}")))?;
 
     let mut casbin_service = CasbinService::new(m, a)
         .await
diff --git a/src/startup.rs b/src/startup.rs
index 8c872f31..35481927 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -23,7 +23,7 @@ pub async fn run(
     let mq_manager = helpers::MqManager::try_new(settings.amqp.connection_string())?;
     let mq_manager = web::Data::new(mq_manager);
 
-    let access_control_manager = middleware::access_manager::try_new().await?;
+    let access_control_manager = middleware::access_manager::try_new(settings.database.connection_string()).await?;
 
     let server = HttpServer::new(move || {
         App::new()

From 30fa18beaef6bb7321fc4ed9b187cef2766c9e1c Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Tue, 30 Jan 2024 09:37:29 +0200
Subject: [PATCH 158/284] stack form fields change, network id is added,
 replace net name with net id for every service

---
 src/forms/stack.rs                          | 45 +++++++++++++++--
 src/helpers/stack/builder.rs                | 54 ++++++++++++---------
 tests/custom-stack-payload-no-networks.json |  2 +
 tests/custom.json                           |  0
 4 files changed, 73 insertions(+), 28 deletions(-)
 create mode 100644 tests/custom-stack-payload-no-networks.json
 create mode 100644 tests/custom.json

diff --git a/src/forms/stack.rs b/src/forms/stack.rs
index fe04381c..ddab46c6 100644
--- a/src/forms/stack.rs
+++ b/src/forms/stack.rs
@@ -2,8 +2,10 @@ use serde::{Deserialize, Serialize};
 use serde_json::Value;
 use serde_valid::Validate;
 use std::fmt;
+use indexmap::IndexMap;
 use regex::Regex;
 use crate::helpers::dockerhub::DockerHub;
+use crate::helpers::stack::dctypes::SingleValue;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Role {
@@ -268,10 +270,6 @@ pub struct Custom {
     pub networks: ComposeNetworks, // all networks
 }
 
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct Network {
-    name: String,
-}
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct App {
@@ -367,9 +365,46 @@ pub struct ServiceNetworks {
     pub network: Option<Vec<String>>,
 }
 
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct NetworkDriver {
+    // not implemented
+}
+
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct Network {
+    pub(crate) id: String,
+    pub(crate) attachable: Option<bool>,
+    pub(crate) driver: Option<String>,
+    pub(crate) driver_opts: Option<NetworkDriver>,
+    pub(crate) enable_ipv6: Option<bool>,
+    pub(crate) internal: Option<bool>,
+    pub(crate) external: Option<bool>,
+    pub(crate) ipam: Option<String>,
+    pub(crate) labels: Option<String>,
+    pub(crate) name: String,
+}
+
+impl Default for Network {
+    fn default() -> Self {
+        // The case when we need at least one external network to be preconfigured
+        Network {
+            id: "default_network".to_string(),
+            attachable: None,
+            driver: None,
+            driver_opts: Default::default(),
+            enable_ipv6: None,
+            internal: None,
+            external: Some(true),
+            ipam: None,
+            labels: None,
+            name: "default_network".to_string(),
+        }
+    }
+}
+
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct ComposeNetworks {
-    pub networks: Option<Vec<String>>,
+    pub networks: Option<Vec<Network>>,
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index 23d1bd91..28c74f51 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -5,7 +5,7 @@ use crate::helpers::stack::dctypes::{Compose, Port, Ports, PublishedPort, Servic
                                      ComposeNetworks, MapOrEmpty, ComposeNetworkSettingDetails,
                                      NetworkSettings};
 use serde_yaml;
-use crate::forms::{StackForm, stack, App, Volume};
+use crate::forms::{StackForm, stack, App, Volume, Network, NetworkDriver};
 use crate::models::stack::Stack;
 #[derive(Clone, Debug)]
 struct Config {}
@@ -101,12 +101,10 @@ impl TryInto<Port> for stack::Port {
 impl TryInto<Networks> for stack::ServiceNetworks {
     type Error = ();
     fn try_into(self) -> Result<Networks, Self::Error> {
-        let mut default_networks = vec!["default_network".to_string()];
+        let mut default_networks = vec![];
         let nets = match self.network {
             Some(mut _nets) => {
-                if !_nets.contains(&"default_network".to_string()) {
                     _nets.append(&mut default_networks);
-                }
                 _nets
             }
             None => {
@@ -178,18 +176,42 @@ impl TryIntoService for App {
     }
 }
 
+impl Into<IndexMap<String, Option<SingleValue>>> for NetworkDriver {
+    fn into(self) -> IndexMap<String, Option<SingleValue>> {
+        IndexMap::new()
+    }
+}
+
+impl Into<NetworkSettings> for Network {
+
+    fn into(self) -> NetworkSettings {
+
+        NetworkSettings {
+            attachable: self.attachable.unwrap_or(false),
+            driver: self.driver.clone(),
+            driver_opts: self.driver_opts.unwrap_or_default().into(), // @todo
+            enable_ipv6: self.enable_ipv6.unwrap_or(false),
+            internal: self.internal.unwrap_or(false),
+            external: Some(ComposeNetwork::Bool(self.external.unwrap_or(false))),
+            ipam: None,                                                       // @todo
+            labels: Default::default(),
+            name: Some(self.name.clone()),
+        }
+    }
+}
 impl Into<IndexMap<String, MapOrEmpty<NetworkSettings>>> for stack::ComposeNetworks {
     fn into(self) -> IndexMap<String, MapOrEmpty<NetworkSettings>> {
 
-        let mut default_network = vec!["default_network".to_string()];
+        // let mut default_networks = vec![Network::default()];
+        let mut default_networks = vec![];
 
         let networks = match self.networks {
             None => {
-                default_network
+                default_networks
             }
             Some(mut nets) => {
-                if !nets.contains(&"default_network".to_string()) {
-                    nets.append(&mut default_network);
+                if !nets.is_empty() {
+                    nets.append(&mut default_networks);
                 }
                 nets
             }
@@ -198,21 +220,7 @@ impl Into<IndexMap<String, MapOrEmpty<NetworkSettings>>> for stack::ComposeNetwo
         let networks = networks
             .into_iter()
             .map(|net| {
-                (net,
-                 MapOrEmpty::Map(
-                     NetworkSettings {
-                         attachable: false,
-                         driver: None,
-                         driver_opts: Default::default(),
-                         enable_ipv6: false,
-                         internal: false,
-                         // external: None,
-                         external: Some(ComposeNetwork::Bool(true)),
-                         ipam: None,
-                         labels: Default::default(),
-                         name: Some("default_network".to_string()),
-                     }
-                 ))
+                (net.name.clone(), MapOrEmpty::Map(net.into()))
             }
             )
             .collect::<IndexMap<String, _>>();
diff --git a/tests/custom-stack-payload-no-networks.json b/tests/custom-stack-payload-no-networks.json
new file mode 100644
index 00000000..2ec622ba
--- /dev/null
+++ b/tests/custom-stack-payload-no-networks.json
@@ -0,0 +1,2 @@
+{"commonDomain":"","domainList":{},"region":"fsn1","zone":null,"server":"cx11","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":false,"disk_type":"pd-standart","cloud_token":"****************************","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"networks":[{
+  "name": "default_network"}, {"name":"mynetwork"}],"web":[{"name":"MyApp","code":"myapp","domain":"myapp.com","shared_ports":[{"host_port":"","container_port":"8080"}],"versions":[],"custom":true,"type":"web","network":["default_network", "mynetwork"],"restart":"no","timestamp":"2024-01-15T09:47:46.580Z","_id":"lreqrvv81ifrmng39","dockerhub_user":"","dockerhub_name":"nginx", "dockerhub_password":"", "dockerhub_image": "None", "environment":[{"key":"SOMEENVVAR1","value":"SOMEENVVAR_VALUE1"},{"key":"SOMEENVVAR2","value":"SOMEENVVAR_VALUE2"}],"volumes":[]}],"feature":[],"service":[],"servers_count":3,"project_git_url":"info@optimum-web.com","project_name":"Sample2024","custom_stack_code":"sample2024"}}
\ No newline at end of file
diff --git a/tests/custom.json b/tests/custom.json
new file mode 100644
index 00000000..e69de29b

From 87db191240caab99cab94e93ae724b3cdd1250ad Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Tue, 30 Jan 2024 18:13:18 +0200
Subject: [PATCH 159/284] issue-auth casbin

---
 access_control.conf.dist          | 14 ++++++++++++++
 rbac/rbac_with_pattern_model.conf | 14 --------------
 rbac/rbac_with_pattern_policy.csv |  6 ------
 src/middleware/access_manager.rs  |  2 +-
 src/middleware/trydirect.rs       |  9 +++++----
 5 files changed, 20 insertions(+), 25 deletions(-)
 create mode 100644 access_control.conf.dist
 delete mode 100644 rbac/rbac_with_pattern_model.conf
 delete mode 100644 rbac/rbac_with_pattern_policy.csv

diff --git a/access_control.conf.dist b/access_control.conf.dist
new file mode 100644
index 00000000..459a15fc
--- /dev/null
+++ b/access_control.conf.dist
@@ -0,0 +1,14 @@
+[request_definition]
+r = sub, obj, act
+
+[policy_definition]
+p = sub, obj, act
+
+[role_definition]
+g = _, _
+
+[policy_effect]
+e = some(where (p.eft == allow))
+
+[matchers]
+m = g(r.sub, p.sub) && r.obj == p.obj && regexMatch(r.act, p.act)
diff --git a/rbac/rbac_with_pattern_model.conf b/rbac/rbac_with_pattern_model.conf
deleted file mode 100644
index 5d047154..00000000
--- a/rbac/rbac_with_pattern_model.conf
+++ /dev/null
@@ -1,14 +0,0 @@
-[request_definition]
-r = sub, dom, obj, act
-
-[policy_definition]
-p = sub, dom, obj, act
-
-[role_definition]
-g = _, _, _
-
-[policy_effect]
-e = some(where (p.eft == allow))
-
-[matchers]
-m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && regexMatch(r.act, p.act)
diff --git a/rbac/rbac_with_pattern_policy.csv b/rbac/rbac_with_pattern_policy.csv
deleted file mode 100644
index 7526184c..00000000
--- a/rbac/rbac_with_pattern_policy.csv
+++ /dev/null
@@ -1,6 +0,0 @@
-p, admin, domain1, /pen/1, GET
-p, admin, domain1, /pen/2, GET
-p, admin, domain2, /book/1, GET
-p, admin, domain2, /book/2, GET
-g, alice, admin, domain1
-g, bob, admin, domain2
diff --git a/src/middleware/access_manager.rs b/src/middleware/access_manager.rs
index 82301cd1..47601146 100644
--- a/src/middleware/access_manager.rs
+++ b/src/middleware/access_manager.rs
@@ -10,7 +10,7 @@ use std::io::{Error, ErrorKind};
 use sqlx_adapter::SqlxAdapter;
 
 pub async fn try_new(db_connection_address: String) -> Result<CasbinService, Error> {
-    let m = DefaultModel::from_file("rbac/rbac_with_pattern_model.conf")
+    let m = DefaultModel::from_file("access_control.conf")
         .await
         .map_err(|err| Error::new(ErrorKind::Other, format!("{err:?}")))?;
     let a = SqlxAdapter::new(db_connection_address, 8)
diff --git a/src/middleware/trydirect.rs b/src/middleware/trydirect.rs
index 0afea4d8..1c61049f 100644
--- a/src/middleware/trydirect.rs
+++ b/src/middleware/trydirect.rs
@@ -20,12 +20,13 @@ pub async fn bearer_guard( req: ServiceRequest, credentials: BearerAuth) -> Resu
         return Err((JsonResponse::<i32>::build().unauthorized("user already logged"), req));
     }
 
-    let vals = actix_casbin_auth::CasbinVals { //todo
+    let accesscontrol_vals = actix_casbin_auth::CasbinVals {
         subject: String::from("alice"),
-        domain: Some("domain1".to_string()),
+        domain: None,
     };
-    let result = req.extensions_mut().insert(vals); //todo
-    tracing::error!("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA {}", result.is_some()); //todo
+    if req.extensions_mut().insert(accesscontrol_vals).is_some() {
+        return Err((JsonResponse::<i32>::build().unauthorized("sth wrong with access control"), req));
+    }
 
     Ok(req)
 }

From 8e67dca8d1d4704cb503bf54f2913fedd2bcc064 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Thu, 1 Feb 2024 12:08:46 +0200
Subject: [PATCH 160/284] default_network is already created by an ansible role
 on TD side and is always external=True

---
 src/helpers/stack/builder.rs | 54 ++++++++++++++++++++++++++++++++----
 1 file changed, 49 insertions(+), 5 deletions(-)

diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index 28c74f51..1466fe13 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -5,6 +5,7 @@ use crate::helpers::stack::dctypes::{Compose, Port, Ports, PublishedPort, Servic
                                      ComposeNetworks, MapOrEmpty, ComposeNetworkSettingDetails,
                                      NetworkSettings};
 use serde_yaml;
+use tracing::Value;
 use crate::forms::{StackForm, stack, App, Volume, Network, NetworkDriver};
 use crate::models::stack::Stack;
 #[derive(Clone, Debug)]
@@ -186,14 +187,18 @@ impl Into<NetworkSettings> for Network {
 
     fn into(self) -> NetworkSettings {
 
+        // default_network is always external=true
+        let is_default = self.name == String::from("default_network");
+        let external = is_default || self.external.unwrap_or(false);
+
         NetworkSettings {
             attachable: self.attachable.unwrap_or(false),
             driver: self.driver.clone(),
             driver_opts: self.driver_opts.unwrap_or_default().into(), // @todo
             enable_ipv6: self.enable_ipv6.unwrap_or(false),
             internal: self.internal.unwrap_or(false),
-            external: Some(ComposeNetwork::Bool(self.external.unwrap_or(false))),
-            ipam: None,                                                       // @todo
+            external: Some(ComposeNetwork::Bool(external)),
+            ipam: None,                                               // @todo
             labels: Default::default(),
             name: Some(self.name.clone()),
         }
@@ -278,6 +283,34 @@ pub fn extract_named_volumes(app: App) -> IndexMap<String, MapOrEmpty<ComposeVol
     named_volumes
 }
 
+fn matches_network_by_id(id: &String, networks: &Vec<Network>) -> Option<String> {
+
+    for n in networks.into_iter() {
+        if id == &n.id {
+            tracing::debug!("matches:  {:?}", n.name);
+            return Some(n.name.clone());
+        }
+    }
+    None
+}
+
+pub fn replace_id_with_name(service_networks: Networks, all_networks: &Vec<Network>) -> Vec<String> {
+
+    match service_networks {
+        Networks::Simple(nets) => {
+            nets
+                .iter()
+                .map(|id| {
+                    if let Some(name) = matches_network_by_id(&id, all_networks) {
+                        name
+                    } else { "".to_string() }
+                })
+                .collect::<Vec<String>>()
+        },
+        _ => vec![]
+    }
+}
+
 impl DcBuilder {
 
     pub fn new(stack: Stack) -> Self {
@@ -299,15 +332,23 @@ impl DcBuilder {
 
         match _stack {
             Ok(apps) => {
+                let all_networks = &apps.custom.networks.networks.clone().unwrap_or(vec![]);
+
                 for app_type in &apps.custom.web {
-                    let service = app_type.app.try_into_service();
+                    let mut service = app_type.app.try_into_service();
+                    let service_networks = service.networks.clone();
+                    let networks = replace_id_with_name(service_networks, all_networks);
+                    service.networks = Networks::Simple(networks);
                     services.insert(app_type.app.code.clone().to_owned(), Some(service));
                     named_volumes.extend(extract_named_volumes(app_type.app.clone()));
                 }
 
                 if let Some(srvs) = apps.custom.service {
                     for app_type in srvs {
-                        let service = app_type.app.try_into_service();
+                        let mut service = app_type.app.try_into_service();
+                        let service_networks = service.networks.clone();
+                        let networks = replace_id_with_name(service_networks, all_networks);
+                        service.networks = Networks::Simple(networks);
                         services.insert(app_type.app.code.clone().to_owned(), Some(service));
                         named_volumes.extend(extract_named_volumes(app_type.app.clone()));
                     }
@@ -315,7 +356,10 @@ impl DcBuilder {
 
                 if let Some(features) = apps.custom.feature {
                     for app_type in features {
-                        let service = app_type.app.try_into_service();
+                        let mut service = app_type.app.try_into_service();
+                        let service_networks = service.networks.clone();
+                        let networks = replace_id_with_name(service_networks, all_networks);
+                        service.networks = Networks::Simple(networks);
                         services.insert(app_type.app.code.clone().to_owned(), Some(service));
                         named_volumes.extend(extract_named_volumes(app_type.app.clone()));
                     }

From 5e150d3f63a5a97ebfa5b818f7d2f1b3df404b09 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Sun, 4 Feb 2024 09:06:03 +0200
Subject: [PATCH 161/284] tests json sample data

---
 tests/custom-stack-payload-no-networks.json | 3 +--
 tests/custom.json                           | 1 +
 tests/dockerhub.rs                          | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/tests/custom-stack-payload-no-networks.json b/tests/custom-stack-payload-no-networks.json
index 2ec622ba..2f81246b 100644
--- a/tests/custom-stack-payload-no-networks.json
+++ b/tests/custom-stack-payload-no-networks.json
@@ -1,2 +1 @@
-{"commonDomain":"","domainList":{},"region":"fsn1","zone":null,"server":"cx11","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":false,"disk_type":"pd-standart","cloud_token":"****************************","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"networks":[{
-  "name": "default_network"}, {"name":"mynetwork"}],"web":[{"name":"MyApp","code":"myapp","domain":"myapp.com","shared_ports":[{"host_port":"","container_port":"8080"}],"versions":[],"custom":true,"type":"web","network":["default_network", "mynetwork"],"restart":"no","timestamp":"2024-01-15T09:47:46.580Z","_id":"lreqrvv81ifrmng39","dockerhub_user":"","dockerhub_name":"nginx", "dockerhub_password":"", "dockerhub_image": "None", "environment":[{"key":"SOMEENVVAR1","value":"SOMEENVVAR_VALUE1"},{"key":"SOMEENVVAR2","value":"SOMEENVVAR_VALUE2"}],"volumes":[]}],"feature":[],"service":[],"servers_count":3,"project_git_url":"info@optimum-web.com","project_name":"Sample2024","custom_stack_code":"sample2024"}}
\ No newline at end of file
+{"commonDomain":"","domainList":{},"region":"fsn1","zone":null,"server":"cx11","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":false,"disk_type":"pd-standart","cloud_token":"****************************","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"name":"MyApp","code":"myapp","domain":"myapp.com","shared_ports":[{"host_port":"","container_port":"8080"}],"versions":[],"custom":true,"type":"web","network":null,"restart":"no","timestamp":"2024-01-15T09:47:46.580Z","_id":"lreqrvv81ifrmng39","dockerhub_user":"","dockerhub_name":"nginx", "dockerhub_password":"", "dockerhub_image": "None", "environment":[{"key":"SOMEENVVAR1","value":"SOMEENVVAR_VALUE1"},{"key":"SOMEENVVAR2","value":"SOMEENVVAR_VALUE2"}],"volumes":[]}],"feature":[],"service":[],"servers_count":3,"project_git_url":"info@optimum-web.com","project_name":"Sample2024-1","custom_stack_code":"sample2024-1"}}
\ No newline at end of file
diff --git a/tests/custom.json b/tests/custom.json
index e69de29b..952b1f92 100644
--- a/tests/custom.json
+++ b/tests/custom.json
@@ -0,0 +1 @@
+{"commonDomain":"","domainList":{},"region":"fsn1","zone":null,"server":"cx11","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":true,"disk_type":"pd-standart","cloud_token":"*********","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"networks":[{"name":"default_network","id":"ls005v9r2xn6l3d2s"},{"name":"lkhlkjhlkjh","id":"ls025c5f1ld1u2tnn"},{"name":"samples","id":"ls007dio3aq1uh6ad"}],"web":[{"name":"samplecom","code":"samplecom","domain":"sample.com","shared_ports":[{"host_port":"80","container_port":"8080"}],"versions":[],"custom":true,"type":"web","network":["ls005v9r2xn6l3d2s","ls025c5f1ld1u2tnn","ls007dio3aq1uh6ad"],"restart":"always","timestamp":"2024-01-30T06:53:55.713Z","_id":"ls0063cx3h9cks1ue","dockerhub_name":"nginx"}],"feature":[],"service":[{"_etag":null,"_id":"ls086eg8szwxxcph","_created":"2020-06-19T13:07:24.228389","_updated":"2024-01-23T11:43:30.452364","name":"PostgreSQL","code":"postgres","role":["postgres"],"type":"service","default":null,"popularity":null,"descr":null,"ports":{"private":["5432"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":576,"height":594,"image":"fd23f54c-e250-4228-8d56-7e5d93ffb925.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":null,"plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.0","ram_size":null,"disk_size":null,"dockerhub_image":"postgres","form":null,"requirements":null,"docker_image_is_internal":true,"category":[null],"group":[{"_etag":null,"_id":12,"_created":null,"_updated":null,"name":"Database","code":"database"}],"versions":[{"_id":458,"name":"15","version":"15","update_status":"published","tag":"15"},{"_id":288,"name":"10.22","version":"10.22","update_status":"published","tag":"10.22"},{"_id":303,"name":"13.8","version":"13.8","update_status":"published","tag":"13.8"},{"_id":266,"name":"11","version":"11","update_status":"published","tag":"11"},{"_id":267,"name":"12.12","version":"12.12","update_status":"published","tag":"12.12"},{"_id":38,"name":"14.5","version":"14.5","update_status":"published","tag":"14.5"},{"_id":596,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"}],"links":[],"shared_ports":[{"host_port":"","container_port":"5432"}],"version":{"_id":596,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"},"network":["ls025c5f1ld1u2tnn","ls007dio3aq1uh6ad"],"restart":"always","timestamp":"2024-01-30T10:38:07.016Z","domain":""}],"servers_count":3,"project_name":"sampletest","custom_stack_code":"sampletest"}}
\ No newline at end of file
diff --git a/tests/dockerhub.rs b/tests/dockerhub.rs
index f1095822..200b665d 100644
--- a/tests/dockerhub.rs
+++ b/tests/dockerhub.rs
@@ -8,7 +8,7 @@ use serde_yaml;
 use stacker::forms::Volume;
 
 const DOCKER_USERNAME: &str = "trydirect";
-const DOCKER_PASSWORD: &str = "*******";
+const DOCKER_PASSWORD: &str = "***************";
 //  Unit Test
 
 // #[test]

From 2994b0481093f75211b64bd39a1b7ba6f3f2e66c Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Sun, 4 Feb 2024 09:06:19 +0200
Subject: [PATCH 162/284] tests json sample data

---
 tests/custom-stack-payload.json | 2 ++
 1 file changed, 2 insertions(+)
 create mode 100644 tests/custom-stack-payload.json

diff --git a/tests/custom-stack-payload.json b/tests/custom-stack-payload.json
new file mode 100644
index 00000000..2ec622ba
--- /dev/null
+++ b/tests/custom-stack-payload.json
@@ -0,0 +1,2 @@
+{"commonDomain":"","domainList":{},"region":"fsn1","zone":null,"server":"cx11","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":false,"disk_type":"pd-standart","cloud_token":"****************************","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"networks":[{
+  "name": "default_network"}, {"name":"mynetwork"}],"web":[{"name":"MyApp","code":"myapp","domain":"myapp.com","shared_ports":[{"host_port":"","container_port":"8080"}],"versions":[],"custom":true,"type":"web","network":["default_network", "mynetwork"],"restart":"no","timestamp":"2024-01-15T09:47:46.580Z","_id":"lreqrvv81ifrmng39","dockerhub_user":"","dockerhub_name":"nginx", "dockerhub_password":"", "dockerhub_image": "None", "environment":[{"key":"SOMEENVVAR1","value":"SOMEENVVAR_VALUE1"},{"key":"SOMEENVVAR2","value":"SOMEENVVAR_VALUE2"}],"volumes":[]}],"feature":[],"service":[],"servers_count":3,"project_git_url":"info@optimum-web.com","project_name":"Sample2024","custom_stack_code":"sample2024"}}
\ No newline at end of file

From 05f0aefcd65dcee0c38c4ff1cd7eb9f9eccdd969 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Sun, 4 Feb 2024 10:12:51 +0200
Subject: [PATCH 163/284] local merge

---
 .gitignore |  1 +
 Cargo.lock | 22 ++++++++++++++++------
 2 files changed, 17 insertions(+), 6 deletions(-)

diff --git a/.gitignore b/.gitignore
index 32da6d82..eb15c24c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 target
 .idea/
 files
+access_control.conf
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index f844c9c0..7dc72c45 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -753,9 +753,9 @@ dependencies = [
 
 [[package]]
 name = "casbin"
-version = "2.1.0"
+version = "2.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53fd06051bc34aa7ee753647dbd36506fa9f84026993d9871e65e0ee71ac3632"
+checksum = "b71063d3ee2f5ecc89229ccade0f3f8fb413b5e3978124a38b611216f91dd7c9"
 dependencies = [
  "async-trait",
  "fixedbitset",
@@ -2806,9 +2806,9 @@ checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0"
 
 [[package]]
 name = "rhai"
-version = "1.16.3"
+version = "1.17.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3625f343d89990133d013e39c46e350915178cf94f1bec9f49b0cbef98a3e3c"
+checksum = "f6273372244d04a8a4b0bec080ea1e710403e88c5d9d83f9808b2bfa64f0982a"
 dependencies = [
  "ahash 0.8.7",
  "bitflags 2.4.2",
@@ -2819,13 +2819,14 @@ dependencies = [
  "serde",
  "smallvec",
  "smartstring",
+ "thin-vec",
 ]
 
 [[package]]
 name = "rhai_codegen"
-version = "1.6.0"
+version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "853977598f084a492323fe2f7896b4100a86284ee8473612de60021ea341310f"
+checksum = "9db7f8dc4c9d48183a17ce550574c42995252b82d267eaca3fcd1b979159856c"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -3583,6 +3584,15 @@ dependencies = [
  "windows-sys 0.52.0",
 ]
 
+[[package]]
+name = "thin-vec"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a38c90d48152c236a3ab59271da4f4ae63d678c5d7ad6b7714d7cb9760be5e4b"
+dependencies = [
+ "serde",
+]
+
 [[package]]
 name = "thiserror"
 version = "1.0.56"

From 769f5a3aff6b7689ebf88ba43a474b47dca4a47d Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Mon, 5 Feb 2024 17:58:30 +0200
Subject: [PATCH 164/284] 30-access-policies app domain

---
 Cargo.lock                  | 138 +++++++++++++++++++++---------------
 access_control.conf.dist    |   8 +--
 src/middleware/client.rs    |   8 +++
 src/middleware/trydirect.rs |   2 +-
 src/startup.rs              |   1 +
 5 files changed, 96 insertions(+), 61 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index f844c9c0..1dc15f9e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5,7 +5,7 @@ version = 3
 [[package]]
 name = "actix-casbin-auth"
 version = "0.4.4"
-source = "git+https://github.com/smart--petea/actix-casbin-auth.git?branch=dirty-master#795c1209e4dea6078a787ccf7a882bd5d1148be7"
+source = "git+https://github.com/smart--petea/actix-casbin-auth.git?branch=dirty-master#5b76cc4e0df68ccee02c563b947a788ab1cb5193"
 dependencies = [
  "actix-service",
  "actix-web",
@@ -16,11 +16,11 @@ dependencies = [
 
 [[package]]
 name = "actix-codec"
-version = "0.5.1"
+version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "617a8268e3537fe1d8c9ead925fca49ef6400927ee7bc26750e90ecee14ce4b8"
+checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a"
 dependencies = [
- "bitflags 1.3.2",
+ "bitflags 2.4.2",
  "bytes",
  "futures-core",
  "futures-sink",
@@ -48,9 +48,9 @@ dependencies = [
 
 [[package]]
 name = "actix-http"
-version = "3.5.1"
+version = "3.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "129d4c88e98860e1758c5de288d1632b07970a16d59bdf7b8d66053d582bb71f"
+checksum = "d223b13fd481fc0d1f83bb12659ae774d9e3601814c68a0bc539731698cca743"
 dependencies = [
  "actix-codec",
  "actix-rt",
@@ -158,9 +158,9 @@ dependencies = [
 
 [[package]]
 name = "actix-web"
-version = "4.4.1"
+version = "4.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e43428f3bf11dee6d166b00ec2df4e3aa8cc1606aaa0b7433c146852e2f4e03b"
+checksum = "43a6556ddebb638c2358714d853257ed226ece6023ef9364f23f0c70737ea984"
 dependencies = [
  "actix-codec",
  "actix-http",
@@ -192,7 +192,7 @@ dependencies = [
  "serde_urlencoded",
  "smallvec",
  "socket2 0.5.5",
- "time 0.3.31",
+ "time 0.3.34",
  "url",
 ]
 
@@ -372,9 +372,9 @@ dependencies = [
 
 [[package]]
 name = "anstyle"
-version = "1.0.4"
+version = "1.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
+checksum = "2faccea4cc4ab4a667ce676a30e8ec13922a692c99bb8f5b11f1502c72e04220"
 
 [[package]]
 name = "anstyle-parse"
@@ -517,7 +517,7 @@ dependencies = [
  "futures-lite 2.2.0",
  "parking",
  "polling 3.3.2",
- "rustix 0.38.30",
+ "rustix 0.38.31",
  "slab",
  "tracing",
  "windows-sys 0.52.0",
@@ -753,9 +753,9 @@ dependencies = [
 
 [[package]]
 name = "casbin"
-version = "2.1.0"
+version = "2.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53fd06051bc34aa7ee753647dbd36506fa9f84026993d9871e65e0ee71ac3632"
+checksum = "b71063d3ee2f5ecc89229ccade0f3f8fb413b5e3978124a38b611216f91dd7c9"
 dependencies = [
  "async-trait",
  "fixedbitset",
@@ -930,7 +930,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
 dependencies = [
  "percent-encoding",
- "time 0.3.31",
+ "time 0.3.34",
  "version_check",
 ]
 
@@ -1265,7 +1265,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6e366950e7785fe0e404901a2b4e5c09ebd6656767f0c2167e34c5068ce0cc2d"
 dependencies = [
  "derive_builder 0.13.0",
- "indexmap 2.2.1",
+ "indexmap 2.2.2",
  "serde",
  "serde_yaml",
 ]
@@ -1651,7 +1651,7 @@ dependencies = [
  "futures-sink",
  "futures-util",
  "http",
- "indexmap 2.2.1",
+ "indexmap 2.2.2",
  "slab",
  "tokio",
  "tokio-util",
@@ -1828,9 +1828,9 @@ dependencies = [
 
 [[package]]
 name = "iana-time-zone"
-version = "0.1.59"
+version = "0.1.60"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539"
+checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
 dependencies = [
  "android_system_properties",
  "core-foundation-sys",
@@ -1878,9 +1878,9 @@ dependencies = [
 
 [[package]]
 name = "indexmap"
-version = "2.2.1"
+version = "2.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "433de089bd45971eecf4668ee0ee8f4cec17db4f8bd8f7bc3197a6ce37aa7d9b"
+checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520"
 dependencies = [
  "equivalent",
  "hashbrown 0.14.3",
@@ -1940,9 +1940,9 @@ dependencies = [
 
 [[package]]
 name = "itertools"
-version = "0.12.0"
+version = "0.12.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0"
+checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
 dependencies = [
  "either",
 ]
@@ -2019,9 +2019,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 
 [[package]]
 name = "libc"
-version = "0.2.152"
+version = "0.2.153"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
+checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
 
 [[package]]
 name = "libredox"
@@ -2139,9 +2139,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
 
 [[package]]
 name = "miniz_oxide"
-version = "0.7.1"
+version = "0.7.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
 dependencies = [
  "adler",
 ]
@@ -2202,6 +2202,12 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "num-conv"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
 [[package]]
 name = "num-traits"
 version = "0.2.17"
@@ -2387,9 +2393,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
 
 [[package]]
 name = "pest"
-version = "2.7.6"
+version = "2.7.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f200d8d83c44a45b21764d1916299752ca035d15ecd46faca3e9a2a2bf6ad06"
+checksum = "219c0dcc30b6a27553f9cc242972b67f75b60eb0db71f0b5462f38b058c41546"
 dependencies = [
  "memchr",
  "thiserror",
@@ -2398,9 +2404,9 @@ dependencies = [
 
 [[package]]
 name = "pest_derive"
-version = "2.7.6"
+version = "2.7.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bcd6ab1236bbdb3a49027e920e693192ebfe8913f6d60e294de57463a493cfde"
+checksum = "22e1288dbd7786462961e69bfd4df7848c1e37e8b74303dbdab82c3a9cdd2809"
 dependencies = [
  "pest",
  "pest_generator",
@@ -2408,9 +2414,9 @@ dependencies = [
 
 [[package]]
 name = "pest_generator"
-version = "2.7.6"
+version = "2.7.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a31940305ffc96863a735bef7c7994a00b325a7138fdbc5bda0f1a0476d3275"
+checksum = "1381c29a877c6d34b8c176e734f35d7f7f5b3adaefe940cb4d1bb7af94678e2e"
 dependencies = [
  "pest",
  "pest_meta",
@@ -2421,9 +2427,9 @@ dependencies = [
 
 [[package]]
 name = "pest_meta"
-version = "2.7.6"
+version = "2.7.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7ff62f5259e53b78d1af898941cdcdccfae7385cf7d793a6e55de5d05bb4b7d"
+checksum = "d0934d6907f148c22a3acbda520c7eed243ad7487a30f51f6ce52b58b7077a8a"
 dependencies = [
  "once_cell",
  "pest",
@@ -2437,7 +2443,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
 dependencies = [
  "fixedbitset",
- "indexmap 2.2.1",
+ "indexmap 2.2.2",
 ]
 
 [[package]]
@@ -2526,7 +2532,7 @@ dependencies = [
  "cfg-if",
  "concurrent-queue",
  "pin-project-lite",
- "rustix 0.38.30",
+ "rustix 0.38.31",
  "tracing",
  "windows-sys 0.52.0",
 ]
@@ -2762,9 +2768,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
 
 [[package]]
 name = "reqwest"
-version = "0.11.23"
+version = "0.11.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41"
+checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251"
 dependencies = [
  "base64 0.21.7",
  "bytes",
@@ -2784,9 +2790,11 @@ dependencies = [
  "once_cell",
  "percent-encoding",
  "pin-project-lite",
+ "rustls-pemfile",
  "serde",
  "serde_json",
  "serde_urlencoded",
+ "sync_wrapper",
  "system-configuration",
  "tokio",
  "tokio-native-tls",
@@ -2806,9 +2814,9 @@ checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0"
 
 [[package]]
 name = "rhai"
-version = "1.16.3"
+version = "1.17.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3625f343d89990133d013e39c46e350915178cf94f1bec9f49b0cbef98a3e3c"
+checksum = "f6273372244d04a8a4b0bec080ea1e710403e88c5d9d83f9808b2bfa64f0982a"
 dependencies = [
  "ahash 0.8.7",
  "bitflags 2.4.2",
@@ -2819,13 +2827,14 @@ dependencies = [
  "serde",
  "smallvec",
  "smartstring",
+ "thin-vec",
 ]
 
 [[package]]
 name = "rhai_codegen"
-version = "1.6.0"
+version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "853977598f084a492323fe2f7896b4100a86284ee8473612de60021ea341310f"
+checksum = "9db7f8dc4c9d48183a17ce550574c42995252b82d267eaca3fcd1b979159856c"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2923,9 +2932,9 @@ dependencies = [
 
 [[package]]
 name = "rustix"
-version = "0.38.30"
+version = "0.38.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca"
+checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
 dependencies = [
  "bitflags 2.4.2",
  "errno",
@@ -3187,7 +3196,7 @@ version = "0.9.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "adf8a49373e98a4c5f0ceb5d05aa7c648d75f63774981ed95b7c7443bbd50c6e"
 dependencies = [
- "indexmap 2.2.1",
+ "indexmap 2.2.2",
  "itoa",
  "ryu",
  "serde",
@@ -3320,7 +3329,7 @@ version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ce81b7bd7c4493975347ef60d8c7e8b742d4694f4c49f93e0a12ea263938176c"
 dependencies = [
- "itertools 0.12.0",
+ "itertools 0.12.1",
  "nom",
  "unicode_categories",
 ]
@@ -3454,7 +3463,7 @@ dependencies = [
  "futures-util",
  "glob",
  "hmac",
- "indexmap 2.2.1",
+ "indexmap 2.2.2",
  "lapin",
  "rand 0.8.5",
  "regex",
@@ -3531,6 +3540,12 @@ dependencies = [
  "unicode-ident",
 ]
 
+[[package]]
+name = "sync_wrapper"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
+
 [[package]]
 name = "system-configuration"
 version = "0.5.1"
@@ -3579,10 +3594,19 @@ dependencies = [
  "cfg-if",
  "fastrand 2.0.1",
  "redox_syscall 0.4.1",
- "rustix 0.38.30",
+ "rustix 0.38.31",
  "windows-sys 0.52.0",
 ]
 
+[[package]]
+name = "thin-vec"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a38c90d48152c236a3ab59271da4f4ae63d678c5d7ad6b7714d7cb9760be5e4b"
+dependencies = [
+ "serde",
+]
+
 [[package]]
 name = "thiserror"
 version = "1.0.56"
@@ -3626,12 +3650,13 @@ dependencies = [
 
 [[package]]
 name = "time"
-version = "0.3.31"
+version = "0.3.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e"
+checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749"
 dependencies = [
  "deranged",
  "itoa",
+ "num-conv",
  "powerfmt",
  "serde",
  "time-core",
@@ -3646,10 +3671,11 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
 
 [[package]]
 name = "time-macros"
-version = "0.2.16"
+version = "0.2.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f"
+checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774"
 dependencies = [
+ "num-conv",
  "time-core",
 ]
 
@@ -3679,9 +3705,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
 
 [[package]]
 name = "tokio"
-version = "1.35.1"
+version = "1.36.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
+checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931"
 dependencies = [
  "backtrace",
  "bytes",
@@ -3826,7 +3852,7 @@ dependencies = [
  "log",
  "serde",
  "serde_json",
- "time 0.3.31",
+ "time 0.3.34",
  "tracing",
  "tracing-core",
  "tracing-log 0.1.4",
diff --git a/access_control.conf.dist b/access_control.conf.dist
index 459a15fc..5d047154 100644
--- a/access_control.conf.dist
+++ b/access_control.conf.dist
@@ -1,14 +1,14 @@
 [request_definition]
-r = sub, obj, act
+r = sub, dom, obj, act
 
 [policy_definition]
-p = sub, obj, act
+p = sub, dom, obj, act
 
 [role_definition]
-g = _, _
+g = _, _, _
 
 [policy_effect]
 e = some(where (p.eft == allow))
 
 [matchers]
-m = g(r.sub, p.sub) && r.obj == p.obj && regexMatch(r.act, p.act)
+m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && regexMatch(r.act, p.act)
diff --git a/src/middleware/client.rs b/src/middleware/client.rs
index a6915d55..def95b06 100644
--- a/src/middleware/client.rs
+++ b/src/middleware/client.rs
@@ -96,6 +96,14 @@ where
                 None => {}
             }
 
+            let accesscontrol_vals = actix_casbin_auth::CasbinVals {
+                subject: client_id.to_string(),
+                domain: Some(String::from("app")),
+            };
+            if req.extensions_mut().insert(accesscontrol_vals).is_some() {
+                return Err("sth wrong with access control".to_string());
+            }
+
             Ok(req)
         }
         .then(|req| async move {
diff --git a/src/middleware/trydirect.rs b/src/middleware/trydirect.rs
index 1c61049f..c0703799 100644
--- a/src/middleware/trydirect.rs
+++ b/src/middleware/trydirect.rs
@@ -22,7 +22,7 @@ pub async fn bearer_guard( req: ServiceRequest, credentials: BearerAuth) -> Resu
 
     let accesscontrol_vals = actix_casbin_auth::CasbinVals {
         subject: String::from("alice"),
-        domain: None,
+        domain: Some(String::from("human")),
     };
     if req.extensions_mut().insert(accesscontrol_vals).is_some() {
         return Err((JsonResponse::<i32>::build().unauthorized("sth wrong with access control"), req));
diff --git a/src/startup.rs b/src/startup.rs
index 35481927..1c43fc77 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -42,6 +42,7 @@ pub async fn run(
             )
             .service(
                 web::scope("/test")
+                    .wrap(access_control_manager.clone()) 
                     .wrap(middleware::client::Guard::new())
                     .wrap(Cors::permissive())
                     .service(crate::routes::test::deploy::handler),

From 4d39ea5300f704044156bd3d277d0d6cff7710c3 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 10 Feb 2024 20:00:14 +0200
Subject: [PATCH 165/284] 30-access-policies sqlx-adapter. new version

---
 Cargo.lock | 343 +++++++++++++++++++++++++++++++++++++++++++----------
 Cargo.toml |   3 +-
 2 files changed, 281 insertions(+), 65 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 1dc15f9e..269e5798 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -372,9 +372,9 @@ dependencies = [
 
 [[package]]
 name = "anstyle"
-version = "1.0.5"
+version = "1.0.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2faccea4cc4ab4a667ce676a30e8ec13922a692c99bb8f5b11f1502c72e04220"
+checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
 
 [[package]]
 name = "anstyle-parse"
@@ -433,13 +433,13 @@ dependencies = [
 
 [[package]]
 name = "async-channel"
-version = "2.1.1"
+version = "2.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c"
+checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3"
 dependencies = [
  "concurrent-queue",
- "event-listener 4.0.3",
- "event-listener-strategy",
+ "event-listener 5.0.0",
+ "event-listener-strategy 0.5.0",
  "futures-core",
  "pin-project-lite",
 ]
@@ -464,7 +464,7 @@ version = "2.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c"
 dependencies = [
- "async-channel 2.1.1",
+ "async-channel 2.2.0",
  "async-executor",
  "async-io 2.3.1",
  "async-lock 3.3.0",
@@ -516,7 +516,7 @@ dependencies = [
  "futures-io",
  "futures-lite 2.2.0",
  "parking",
- "polling 3.3.2",
+ "polling 3.4.0",
  "rustix 0.38.31",
  "slab",
  "tracing",
@@ -539,7 +539,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b"
 dependencies = [
  "event-listener 4.0.3",
- "event-listener-strategy",
+ "event-listener-strategy 0.4.0",
  "pin-project-lite",
 ]
 
@@ -581,12 +581,31 @@ dependencies = [
  "num-traits",
 ]
 
+[[package]]
+name = "atoi"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
+dependencies = [
+ "num-traits",
+]
+
 [[package]]
 name = "atomic-waker"
 version = "1.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
 
+[[package]]
+name = "atomic-write-file"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edcdbedc2236483ab103a53415653d6b4442ea6141baf1ffa85df29635e88436"
+dependencies = [
+ "nix",
+ "rand 0.8.5",
+]
+
 [[package]]
 name = "autocfg"
 version = "1.1.0"
@@ -656,7 +675,7 @@ version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118"
 dependencies = [
- "async-channel 2.1.1",
+ "async-channel 2.2.0",
  "async-lock 3.3.0",
  "async-task",
  "fastrand 2.0.1",
@@ -731,9 +750,9 @@ dependencies = [
 
 [[package]]
 name = "cargo-platform"
-version = "0.1.6"
+version = "0.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ceed8ef69d8518a5dda55c07425450b58a4e1946f4951eab6d7191ee86c2443d"
+checksum = "694c8807f2ae16faecc43dc17d74b3eb042482789fd0eb64b39a2e04e087053f"
 dependencies = [
  "serde",
 ]
@@ -825,9 +844,9 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "4.4.18"
+version = "4.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c"
+checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f"
 dependencies = [
  "clap_builder",
  "clap_derive",
@@ -835,21 +854,21 @@ dependencies = [
 
 [[package]]
 name = "clap_builder"
-version = "4.4.18"
+version = "4.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7"
+checksum = "458bf1f341769dfcf849846f65dffdf9146daa56bcd2a47cb4e1de9915567c99"
 dependencies = [
  "anstream",
  "anstyle",
  "clap_lex",
- "strsim",
+ "strsim 0.11.0",
 ]
 
 [[package]]
 name = "clap_derive"
-version = "4.4.7"
+version = "4.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
+checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47"
 dependencies = [
  "heck",
  "proc-macro2",
@@ -859,9 +878,9 @@ dependencies = [
 
 [[package]]
 name = "clap_lex"
-version = "0.6.0"
+version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
+checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
 
 [[package]]
 name = "colorchoice"
@@ -1049,7 +1068,7 @@ dependencies = [
  "ident_case",
  "proc-macro2",
  "quote",
- "strsim",
+ "strsim 0.10.0",
  "syn 1.0.109",
 ]
 
@@ -1284,9 +1303,9 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
 
 [[package]]
 name = "either"
-version = "1.9.0"
+version = "1.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
+checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
 dependencies = [
  "serde",
 ]
@@ -1325,6 +1344,17 @@ dependencies = [
  "version_check",
 ]
 
+[[package]]
+name = "etcetera"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943"
+dependencies = [
+ "cfg-if",
+ "home",
+ "windows-sys 0.48.0",
+]
+
 [[package]]
 name = "event-listener"
 version = "2.5.3"
@@ -1342,6 +1372,17 @@ dependencies = [
  "pin-project-lite",
 ]
 
+[[package]]
+name = "event-listener"
+version = "5.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b72557800024fabbaa2449dd4bf24e37b93702d457a4d4f2b0dd1f0f039f20c1"
+dependencies = [
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite",
+]
+
 [[package]]
 name = "event-listener-strategy"
 version = "0.4.0"
@@ -1352,6 +1393,16 @@ dependencies = [
  "pin-project-lite",
 ]
 
+[[package]]
+name = "event-listener-strategy"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291"
+dependencies = [
+ "event-listener 5.0.0",
+ "pin-project-lite",
+]
+
 [[package]]
 name = "executor-trait"
 version = "2.1.0"
@@ -1502,6 +1553,17 @@ dependencies = [
  "parking_lot 0.11.2",
 ]
 
+[[package]]
+name = "futures-intrusive"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
+dependencies = [
+ "futures-core",
+ "lock_api",
+ "parking_lot 0.12.1",
+]
+
 [[package]]
 name = "futures-io"
 version = "0.3.30"
@@ -1706,9 +1768,9 @@ dependencies = [
 
 [[package]]
 name = "hermit-abi"
-version = "0.3.4"
+version = "0.3.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f"
+checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3"
 
 [[package]]
 name = "hex"
@@ -1734,6 +1796,15 @@ dependencies = [
  "digest",
 ]
 
+[[package]]
+name = "home"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
 [[package]]
 name = "http"
 version = "0.2.11"
@@ -1955,18 +2026,18 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
 
 [[package]]
 name = "jobserver"
-version = "0.1.27"
+version = "0.1.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d"
+checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6"
 dependencies = [
  "libc",
 ]
 
 [[package]]
 name = "js-sys"
-version = "0.3.67"
+version = "0.3.68"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1"
+checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee"
 dependencies = [
  "wasm-bindgen",
 ]
@@ -2182,6 +2253,17 @@ dependencies = [
  "tempfile",
 ]
 
+[[package]]
+name = "nix"
+version = "0.27.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
+dependencies = [
+ "bitflags 2.4.2",
+ "cfg-if",
+ "libc",
+]
+
 [[package]]
 name = "nom"
 version = "7.1.3"
@@ -2210,9 +2292,9 @@ checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
 
 [[package]]
 name = "num-traits"
-version = "0.2.17"
+version = "0.2.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
+checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
 dependencies = [
  "autocfg",
 ]
@@ -2525,9 +2607,9 @@ dependencies = [
 
 [[package]]
 name = "polling"
-version = "3.3.2"
+version = "3.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "545c980a3880efd47b2e262f6a4bb6daad6555cf3367aa9c4e52895f69537a41"
+checksum = "30054e72317ab98eddd8561db0f6524df3367636884b7b21b703e4b280a84a14"
 dependencies = [
  "cfg-if",
  "concurrent-queue",
@@ -3176,7 +3258,7 @@ dependencies = [
  "proc-macro-error",
  "proc-macro2",
  "quote",
- "strsim",
+ "strsim 0.10.0",
  "syn 1.0.109",
 ]
 
@@ -3340,19 +3422,31 @@ version = "0.6.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f8de3b03a925878ed54a954f621e64bf55a3c1bd29652d0d1a17830405350188"
 dependencies = [
- "sqlx-core",
- "sqlx-macros",
+ "sqlx-core 0.6.3",
+ "sqlx-macros 0.6.3",
+]
+
+[[package]]
+name = "sqlx"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dba03c279da73694ef99763320dea58b51095dfe87d001b1d4b5fe78ba8763cf"
+dependencies = [
+ "sqlx-core 0.7.3",
+ "sqlx-macros 0.7.3",
+ "sqlx-postgres",
 ]
 
 [[package]]
 name = "sqlx-adapter"
-version = "0.4.2"
-source = "git+https://github.com/smart--petea/sqlx-adapter.git?branch=dirty-master#5f7cabbde494098a1b460f26c4b74fcbcf2d5857"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e39a25e18b8f9cd4f61b1653be67260d28dcd31b0bd8d3a08ed3ecbe2afa3b84"
 dependencies = [
  "async-trait",
  "casbin",
  "dotenv",
- "sqlx",
+ "sqlx 0.7.3",
 ]
 
 [[package]]
@@ -3362,7 +3456,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fa8241483a83a3f33aa5fff7e7d9def398ff9990b2752b6c6112b83c6d246029"
 dependencies = [
  "ahash 0.7.7",
- "atoi",
+ "atoi 1.0.0",
  "base64 0.13.1",
  "bitflags 1.3.2",
  "byteorder",
@@ -3376,7 +3470,7 @@ dependencies = [
  "event-listener 2.5.3",
  "futures-channel",
  "futures-core",
- "futures-intrusive",
+ "futures-intrusive 0.4.2",
  "futures-util",
  "hashlink",
  "hex",
@@ -3410,6 +3504,47 @@ dependencies = [
  "whoami",
 ]
 
+[[package]]
+name = "sqlx-core"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d84b0a3c3739e220d94b3239fd69fb1f74bc36e16643423bd99de3b43c21bfbd"
+dependencies = [
+ "ahash 0.8.7",
+ "atoi 2.0.0",
+ "byteorder",
+ "bytes",
+ "crc",
+ "crossbeam-queue",
+ "dotenvy",
+ "either",
+ "event-listener 2.5.3",
+ "futures-channel",
+ "futures-core",
+ "futures-intrusive 0.5.0",
+ "futures-io",
+ "futures-util",
+ "hashlink",
+ "hex",
+ "indexmap 2.2.2",
+ "log",
+ "memchr",
+ "native-tls",
+ "once_cell",
+ "paste",
+ "percent-encoding",
+ "serde",
+ "serde_json",
+ "sha2",
+ "smallvec",
+ "sqlformat",
+ "thiserror",
+ "tokio",
+ "tokio-stream",
+ "tracing",
+ "url",
+]
+
 [[package]]
 name = "sqlx-macros"
 version = "0.6.3"
@@ -3426,12 +3561,89 @@ dependencies = [
  "serde",
  "serde_json",
  "sha2",
- "sqlx-core",
+ "sqlx-core 0.6.3",
  "sqlx-rt",
  "syn 1.0.109",
  "url",
 ]
 
+[[package]]
+name = "sqlx-macros"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89961c00dc4d7dffb7aee214964b065072bff69e36ddb9e2c107541f75e4f2a5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "sqlx-core 0.7.3",
+ "sqlx-macros-core",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "sqlx-macros-core"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0bd4519486723648186a08785143599760f7cc81c52334a55d6a83ea1e20841"
+dependencies = [
+ "atomic-write-file",
+ "dotenvy",
+ "either",
+ "heck",
+ "hex",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_json",
+ "sha2",
+ "sqlx-core 0.7.3",
+ "sqlx-postgres",
+ "syn 1.0.109",
+ "tempfile",
+ "tokio",
+ "url",
+]
+
+[[package]]
+name = "sqlx-postgres"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6ac0ac3b7ccd10cc96c7ab29791a7dd236bd94021f31eec7ba3d46a74aa1c24"
+dependencies = [
+ "atoi 2.0.0",
+ "base64 0.21.7",
+ "bitflags 2.4.2",
+ "byteorder",
+ "crc",
+ "dotenvy",
+ "etcetera",
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-util",
+ "hex",
+ "hkdf",
+ "hmac",
+ "home",
+ "itoa",
+ "log",
+ "md-5",
+ "memchr",
+ "once_cell",
+ "rand 0.8.5",
+ "serde",
+ "serde_json",
+ "sha1",
+ "sha2",
+ "smallvec",
+ "sqlx-core 0.7.3",
+ "stringprep",
+ "thiserror",
+ "tracing",
+ "whoami",
+]
+
 [[package]]
 name = "sqlx-rt"
 version = "0.6.3"
@@ -3475,7 +3687,7 @@ dependencies = [
  "serde_valid",
  "serde_yaml",
  "sha2",
- "sqlx",
+ "sqlx 0.6.3",
  "sqlx-adapter",
  "thiserror",
  "tokio",
@@ -3512,6 +3724,12 @@ version = "0.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
 
+[[package]]
+name = "strsim"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
+
 [[package]]
 name = "subtle"
 version = "2.5.0"
@@ -3587,13 +3805,12 @@ dependencies = [
 
 [[package]]
 name = "tempfile"
-version = "3.9.0"
+version = "3.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa"
+checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67"
 dependencies = [
  "cfg-if",
  "fastrand 2.0.1",
- "redox_syscall 0.4.1",
  "rustix 0.38.31",
  "windows-sys 0.52.0",
 ]
@@ -3965,9 +4182,9 @@ dependencies = [
 
 [[package]]
 name = "unicode-segmentation"
-version = "1.10.1"
+version = "1.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
+checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
 
 [[package]]
 name = "unicode_categories"
@@ -4084,9 +4301,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.90"
+version = "0.2.91"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406"
+checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f"
 dependencies = [
  "cfg-if",
  "wasm-bindgen-macro",
@@ -4094,9 +4311,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.90"
+version = "0.2.91"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd"
+checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b"
 dependencies = [
  "bumpalo",
  "log",
@@ -4109,9 +4326,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-futures"
-version = "0.4.40"
+version = "0.4.41"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461"
+checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97"
 dependencies = [
  "cfg-if",
  "js-sys",
@@ -4121,9 +4338,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.90"
+version = "0.2.91"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999"
+checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -4131,9 +4348,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.90"
+version = "0.2.91"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7"
+checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -4144,15 +4361,15 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.90"
+version = "0.2.91"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b"
+checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838"
 
 [[package]]
 name = "web-sys"
-version = "0.3.67"
+version = "0.3.68"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed"
+checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
diff --git a/Cargo.toml b/Cargo.toml
index 89420989..20a127c1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -44,8 +44,7 @@ tokio-stream = "0.1.14"
 actix-http = "3.4.0"
 hmac = "0.12.1"
 sha2 = "0.10.8"
-#sqlx-adapter = { version = "0.4.2", default-features = false, features = ["postgres", "runtime-tokio-native-tls"]}
-sqlx-adapter = { git="https://github.com/smart--petea/sqlx-adapter.git", branch="dirty-master", default-features = false, features = ["postgres"]}
+sqlx-adapter = { version = "1.0.0", default-features = false, features = ["postgres", "runtime-tokio-native-tls"]}
 
 # dctypes
 derive_builder = "0.12.0"

From c53bcb79f77e353633730294dfb1c4b0efca9960 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Wed, 14 Feb 2024 18:18:35 +0200
Subject: [PATCH 166/284] 30-access-policies

---
 access_control.conf.dist    |  8 ++++----
 src/forms/mod.rs            |  1 +
 src/forms/user.rs           |  4 +++-
 src/middleware/client.rs    |  2 +-
 src/middleware/trydirect.rs | 16 +++++++++-------
 src/startup.rs              |  9 ++++++++-
 6 files changed, 26 insertions(+), 14 deletions(-)

diff --git a/access_control.conf.dist b/access_control.conf.dist
index 5d047154..f164af1a 100644
--- a/access_control.conf.dist
+++ b/access_control.conf.dist
@@ -1,14 +1,14 @@
 [request_definition]
-r = sub, dom, obj, act
+r = sub, obj, act
 
 [policy_definition]
-p = sub, dom, obj, act
+p = sub, obj, act
 
 [role_definition]
-g = _, _, _
+g = _, _
 
 [policy_effect]
 e = some(where (p.eft == allow))
 
 [matchers]
-m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && regexMatch(r.act, p.act)
+m = g(r.sub, p.sub) && keyMatch2(r.obj, p.obj) && r.act == p.act
diff --git a/src/forms/mod.rs b/src/forms/mod.rs
index 9647ea6e..273c2a06 100644
--- a/src/forms/mod.rs
+++ b/src/forms/mod.rs
@@ -3,3 +3,4 @@ pub mod stack;
 pub mod user;
 
 pub use rating::*;
+pub use user::UserForm;
diff --git a/src/forms/user.rs b/src/forms/user.rs
index 8ff0b4ce..0fd00246 100644
--- a/src/forms/user.rs
+++ b/src/forms/user.rs
@@ -10,6 +10,8 @@ pub struct UserForm {
     pub user: User,
 }
 
+//todo deref for UserForm. userForm.id, userForm.first_name
+
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 #[serde(rename_all = "camelCase")]
 pub struct User {
@@ -139,4 +141,4 @@ impl TryInto<UserModel> for UserForm {
         })
     }
 
-}
\ No newline at end of file
+}
diff --git a/src/middleware/client.rs b/src/middleware/client.rs
index def95b06..7345223e 100644
--- a/src/middleware/client.rs
+++ b/src/middleware/client.rs
@@ -98,7 +98,7 @@ where
 
             let accesscontrol_vals = actix_casbin_auth::CasbinVals {
                 subject: client_id.to_string(),
-                domain: Some(String::from("app")),
+                domain: None,
             };
             if req.extensions_mut().insert(accesscontrol_vals).is_some() {
                 return Err("sth wrong with access control".to_string());
diff --git a/src/middleware/trydirect.rs b/src/middleware/trydirect.rs
index c0703799..e9ce5eb8 100644
--- a/src/middleware/trydirect.rs
+++ b/src/middleware/trydirect.rs
@@ -1,4 +1,6 @@
-use crate::{models, configuration::Settings, forms::user::UserForm, helpers::JsonResponse};
+use crate::{configuration::Settings, helpers::JsonResponse};
+use crate::models;
+use crate::forms;
 use actix_web::{web, dev::ServiceRequest, Error, HttpMessage};
 use actix_web_httpauth::extractors::bearer::BearerAuth;
 use futures::future::{FutureExt};
@@ -12,20 +14,20 @@ pub async fn bearer_guard( req: ServiceRequest, credentials: BearerAuth) -> Resu
     let user = match fetch_user(settings.auth_url.as_str(), token).await {
         Ok(user) => user,
         Err(err) => {
-            return Err((JsonResponse::<i32>::build().unauthorized(err), req));
+            return Err((JsonResponse::<i32>::build().unauthorized(err), req)); //todo anonymous
         }
     };
 
     if req.extensions_mut().insert(Arc::new(user)).is_some() {
-        return Err((JsonResponse::<i32>::build().unauthorized("user already logged"), req));
+        return Err((JsonResponse::<i32>::build().unauthorized("user already logged"), req)); //todo 500 internal error
     }
 
     let accesscontrol_vals = actix_casbin_auth::CasbinVals {
-        subject: String::from("alice"),
-        domain: Some(String::from("human")),
+        subject: String::from("alice"), //todo username or anonymous
+        domain: None,
     };
     if req.extensions_mut().insert(accesscontrol_vals).is_some() {
-        return Err((JsonResponse::<i32>::build().unauthorized("sth wrong with access control"), req));
+        return Err((JsonResponse::<i32>::build().unauthorized("sth wrong with access control"), req)); //todo 500 internal error
     }
 
     Ok(req)
@@ -47,7 +49,7 @@ async fn fetch_user(auth_url: &str, token: &str) -> Result<models::User, String>
     }
 
     resp
-        .json::<UserForm>()
+        .json::<forms::UserForm>()
         .await
         .map_err(|_err| "can't parse the response body".to_string())?
         .try_into()
diff --git a/src/startup.rs b/src/startup.rs
index 1c43fc77..45f14640 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -28,7 +28,14 @@ pub async fn run(
     let server = HttpServer::new(move || {
         App::new()
             .wrap(TracingLogger::default())
-            .service(web::scope("/health_check").service(crate::routes::health_check))
+            .service(
+                web::scope("/health_check")
+                .wrap(HttpAuthentication::bearer(
+                        middleware::trydirect::bearer_guard,
+                        ))
+                .wrap(Cors::permissive())
+                .service(crate::routes::health_check)
+            )
             .service(
                 web::scope("/client")
                     .wrap(HttpAuthentication::bearer(

From ff82dbf72bbae09dfc0229f4721e3743f591b39c Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 17 Feb 2024 08:03:05 +0200
Subject: [PATCH 167/284] 30-access-policies replace crate::routes::xxx with
 routes::xx

---
 src/startup.rs | 70 +++++++++++++++++++-------------------------------
 1 file changed, 26 insertions(+), 44 deletions(-)

diff --git a/src/startup.rs b/src/startup.rs
index 45f14640..447f2dd2 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -1,5 +1,6 @@
 use crate::configuration::Settings;
 use crate::helpers;
+use crate::routes;
 use actix_cors::Cors;
 use actix_web::dev::Server;
 use actix_web::{
@@ -28,64 +29,45 @@ pub async fn run(
     let server = HttpServer::new(move || {
         App::new()
             .wrap(TracingLogger::default())
-            .service(
-                web::scope("/health_check")
-                .wrap(HttpAuthentication::bearer(
+            /*
+                    .wrap(middleware::client::Guard::new())
+                    .wrap(HttpAuthentication::bearer(
                         middleware::trydirect::bearer_guard,
-                        ))
-                .wrap(Cors::permissive())
-                .service(crate::routes::health_check)
+                    ))
+            */
+            .wrap(access_control_manager.clone()) 
+            .wrap(Cors::permissive())
+            .service(
+                web::scope("/health_check").service(routes::health_check)
             )
             .service(
                 web::scope("/client")
-                    .wrap(HttpAuthentication::bearer(
-                        middleware::trydirect::bearer_guard,
-                    ))
-                    .wrap(Cors::permissive())
-                    .service(crate::routes::client::add_handler)
-                    .service(crate::routes::client::update_handler)
-                    .service(crate::routes::client::enable_handler)
-                    .service(crate::routes::client::disable_handler),
+                    .service(routes::client::add_handler)
+                    .service(routes::client::update_handler)
+                    .service(routes::client::enable_handler)
+                    .service(routes::client::disable_handler),
             )
             .service(
-                web::scope("/test")
-                    .wrap(access_control_manager.clone()) 
-                    .wrap(middleware::client::Guard::new())
-                    .wrap(Cors::permissive())
-                    .service(crate::routes::test::deploy::handler),
+                web::scope("/test").service(routes::test::deploy::handler),
             )
             .service(
-                web::scope("/pen/1")
-                    .wrap(access_control_manager.clone()) 
-                    .wrap(HttpAuthentication::bearer(
-                        middleware::trydirect::bearer_guard,
-                    ))
-                    .wrap(Cors::permissive())
-                    .service(crate::routes::test::casbin::handler),
+                web::scope("/pen/1").service(routes::test::casbin::handler),
             )
             .service(
                 web::scope("/rating")
-                    .wrap(HttpAuthentication::bearer(
-                        middleware::trydirect::bearer_guard,
-                    ))
-                    .wrap(Cors::permissive())
-                    .service(crate::routes::rating::add_handler)
-                    .service(crate::routes::rating::get_handler)
-                    .service(crate::routes::rating::list_handler),
+                    .service(routes::rating::add_handler)
+                    .service(routes::rating::get_handler)
+                    .service(routes::rating::list_handler),
             )
             .service(
                 web::scope("/stack")
-                    .wrap(HttpAuthentication::bearer(
-                        middleware::trydirect::bearer_guard,
-                    ))
-                    .wrap(Cors::permissive())
-                    .service(crate::routes::stack::deploy::add)
-                    .service(crate::routes::stack::compose::add)
-                    .service(crate::routes::stack::compose::admin)
-                    .service(crate::routes::stack::get::item)
-                    .service(crate::routes::stack::get::list)
-                    .service(crate::routes::stack::add::add)
-                    .service(crate::routes::stack::update::update),
+                    .service(routes::stack::deploy::add)
+                    .service(routes::stack::compose::add)
+                    .service(routes::stack::compose::admin)
+                    .service(routes::stack::get::item)
+                    .service(routes::stack::get::list)
+                    .service(routes::stack::add::add)
+                    .service(routes::stack::update::update),
             )
             .app_data(pg_pool.clone())
             .app_data(mq_manager.clone())

From 7e575c699252a2afbe35c0e7037b8d2c7248483c Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 17 Feb 2024 08:12:46 +0200
Subject: [PATCH 168/284] 30-access-policies authorization manager

---
 .../{access_manager.rs => authorization_manager.rs}           | 0
 src/middleware/mod.rs                                         | 2 +-
 src/startup.rs                                                | 4 ++--
 3 files changed, 3 insertions(+), 3 deletions(-)
 rename src/middleware/{access_manager.rs => authorization_manager.rs} (100%)

diff --git a/src/middleware/access_manager.rs b/src/middleware/authorization_manager.rs
similarity index 100%
rename from src/middleware/access_manager.rs
rename to src/middleware/authorization_manager.rs
diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs
index 59debf10..97bbd746 100644
--- a/src/middleware/mod.rs
+++ b/src/middleware/mod.rs
@@ -1,3 +1,3 @@
 pub mod client;
 pub mod trydirect;
-pub mod access_manager;
+pub mod authorization_manager;
diff --git a/src/startup.rs b/src/startup.rs
index 447f2dd2..9df9ab20 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -24,7 +24,7 @@ pub async fn run(
     let mq_manager = helpers::MqManager::try_new(settings.amqp.connection_string())?;
     let mq_manager = web::Data::new(mq_manager);
 
-    let access_control_manager = middleware::access_manager::try_new(settings.database.connection_string()).await?;
+    let authorization_manager = middleware::authorization_manager::try_new(settings.database.connection_string()).await?;
 
     let server = HttpServer::new(move || {
         App::new()
@@ -35,7 +35,7 @@ pub async fn run(
                         middleware::trydirect::bearer_guard,
                     ))
             */
-            .wrap(access_control_manager.clone()) 
+            .wrap(authorization_manager.clone()) 
             .wrap(Cors::permissive())
             .service(
                 web::scope("/health_check").service(routes::health_check)

From eae011695cb9e64d8ece1442f1c2e4c2738ca362 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 17 Feb 2024 08:25:13 +0200
Subject: [PATCH 169/284] 30-access-polices - Authorization/Authentication
 manager

---
 .../{client.rs => authentication/mod.rs}      | 16 +++++-----
 src/middleware/authorization_manager.rs       | 32 -------------------
 src/middleware/mod.rs                         |  4 +--
 src/startup.rs                                | 11 ++-----
 4 files changed, 13 insertions(+), 50 deletions(-)
 rename src/middleware/{client.rs => authentication/mod.rs} (94%)
 delete mode 100644 src/middleware/authorization_manager.rs

diff --git a/src/middleware/client.rs b/src/middleware/authentication/mod.rs
similarity index 94%
rename from src/middleware/client.rs
rename to src/middleware/authentication/mod.rs
index 7345223e..81adf906 100644
--- a/src/middleware/client.rs
+++ b/src/middleware/authentication/mod.rs
@@ -22,15 +22,15 @@ use actix_web::{
 };
 use sqlx::{Pool, Postgres};
 
-pub struct Guard {}
+pub struct Manager {}
 
-impl Guard {
+impl Manager {
     pub fn new() -> Self {
         Self {}
     }
 }
 
-impl<S, B> Transform<S, ServiceRequest> for Guard
+impl<S, B> Transform<S, ServiceRequest> for Manager
 where
     S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
     S::Future: 'static,
@@ -39,21 +39,21 @@ where
     type Response = ServiceResponse<B>;
     type Error = Error;
     type InitError = ();
-    type Transform = GuardMiddleware<S>;
+    type Transform = ManagerMiddleware<S>;
     type Future = Ready<Result<Self::Transform, Self::InitError>>;
 
     fn new_transform(&self, service: S) -> Self::Future {
-        ready(Ok(GuardMiddleware {
+        ready(Ok(ManagerMiddleware {
             service: Arc::new(Mutex::new(service)),
         }))
     }
 }
 
-pub struct GuardMiddleware<S> {
+pub struct ManagerMiddleware<S> {
     service: Arc<Mutex<S>>,
 }
 
-impl<S, B> Service<ServiceRequest> for GuardMiddleware<S>
+impl<S, B> Service<ServiceRequest> for ManagerMiddleware<S>
 where
     S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
     S::Future: 'static,
@@ -66,7 +66,7 @@ where
     fn poll_ready(&self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
         self.service
             .try_lock()
-            .expect("GuardMiddleware was called allready")
+            .expect("Authentication ManagerMiddleware was called allready")
             .poll_ready(ctx)
     }
 
diff --git a/src/middleware/authorization_manager.rs b/src/middleware/authorization_manager.rs
deleted file mode 100644
index 47601146..00000000
--- a/src/middleware/authorization_manager.rs
+++ /dev/null
@@ -1,32 +0,0 @@
-use actix_casbin_auth::{
-    CasbinService,
-    casbin::{
-        DefaultModel,
-        CoreApi,
-        function_map::key_match2
-    }
-};
-use std::io::{Error, ErrorKind};
-use sqlx_adapter::SqlxAdapter;
-
-pub async fn try_new(db_connection_address: String) -> Result<CasbinService, Error> {
-    let m = DefaultModel::from_file("access_control.conf")
-        .await
-        .map_err(|err| Error::new(ErrorKind::Other, format!("{err:?}")))?;
-    let a = SqlxAdapter::new(db_connection_address, 8)
-        .await
-        .map_err(|err| Error::new(ErrorKind::Other, format!("{err:?}")))?;
-
-    let mut casbin_service = CasbinService::new(m, a)
-        .await
-        .map_err(|err| Error::new(ErrorKind::Other, format!("{err:?}")))?;
-
-    casbin_service
-        .write()
-        .await
-        .get_role_manager()
-        .write()
-        .matching_fn(Some(key_match2), None);
-
-    Ok(casbin_service)
-}
diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs
index 97bbd746..5efc32ed 100644
--- a/src/middleware/mod.rs
+++ b/src/middleware/mod.rs
@@ -1,3 +1,3 @@
-pub mod client;
+pub mod authentication;
 pub mod trydirect;
-pub mod authorization_manager;
+pub mod authorization;
diff --git a/src/startup.rs b/src/startup.rs
index 9df9ab20..83b8548b 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -24,18 +24,13 @@ pub async fn run(
     let mq_manager = helpers::MqManager::try_new(settings.amqp.connection_string())?;
     let mq_manager = web::Data::new(mq_manager);
 
-    let authorization_manager = middleware::authorization_manager::try_new(settings.database.connection_string()).await?;
+    let authorization = middleware::authorization::try_new(settings.database.connection_string()).await?;
 
     let server = HttpServer::new(move || {
         App::new()
             .wrap(TracingLogger::default())
-            /*
-                    .wrap(middleware::client::Guard::new())
-                    .wrap(HttpAuthentication::bearer(
-                        middleware::trydirect::bearer_guard,
-                    ))
-            */
-            .wrap(authorization_manager.clone()) 
+            .wrap(middleware::authentication::Manager::new())
+            .wrap(authorization.clone()) 
             .wrap(Cors::permissive())
             .service(
                 web::scope("/health_check").service(routes::health_check)

From e4ad08ab5ed81d94094de3c7a24672769384b44b Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 17 Feb 2024 08:43:02 +0200
Subject: [PATCH 170/284] 30-access-policies get_header

---
 src/middleware/authentication/mod.rs | 23 +++++++++++++----------
 1 file changed, 13 insertions(+), 10 deletions(-)

diff --git a/src/middleware/authentication/mod.rs b/src/middleware/authentication/mod.rs
index 81adf906..c2a1450e 100644
--- a/src/middleware/authentication/mod.rs
+++ b/src/middleware/authentication/mod.rs
@@ -73,8 +73,8 @@ where
     fn call(&self, mut req: ServiceRequest) -> Self::Future {
         let service = self.service.clone();
         async move {
-            let client_id: i32 = get_header(&req, "stacker-id")?;
-            let header_hash: String = get_header(&req, "stacker-hash")?;
+            let client_id: i32 = get_header(&req, "stacker-id")?.unwrap(); //todo
+            let header_hash: String = get_header(&req, "stacker-hash")?.unwrap(); //todo
 
             let db_pool = req.app_data::<web::Data<Pool<Postgres>>>().unwrap().get_ref();
             let client: Client = db_fetch_client(db_pool, client_id).await?;
@@ -121,25 +121,28 @@ where
     }
 }
 
-fn get_header<T>(req: &ServiceRequest, header_name: &'static str) -> Result<T, String>
+fn get_header<T>(req: &ServiceRequest, header_name: &'static str) -> Result<Option<T>, String>
 where
     T: FromStr,
 {
     let header_value = req
         .headers()
-        .get(HeaderName::from_static(header_name))
-        .ok_or(format!("header {header_name} not found"))?;
+        .get(HeaderName::from_static(header_name));
 
-    let header_value: &str = header_value
-        .to_str()
-        .map_err(|_| format!("header {header_name} can't be converted to string"))?; 
+    if header_value.is_none() {
+        return Ok(None);
+    }
 
     header_value
+        .unwrap()
+        .to_str()
+        .map_err(|_| format!("header {header_name} can't be converted to string"))? 
         .parse::<T>()
         .map_err(|_| format!("header {header_name} has wrong type"))
+        .map(|v| Some(v))
 }
 
-async fn db_fetch_client(db_pool: &Pool<Postgres>, client_id: i32) -> Result<Client, String> {
+async fn db_fetch_client(db_pool: &Pool<Postgres>, client_id: i32) -> Result<Client, String> { //todo
     let query_span = tracing::info_span!("Fetching the client by ID");
 
     sqlx::query_as!(
@@ -162,7 +165,7 @@ async fn db_fetch_client(db_pool: &Pool<Postgres>, client_id: i32) -> Result<Cli
 }
 
 async fn compute_body_hash(req: &mut ServiceRequest, client_secret: &[u8]) -> Result<String, String> {
-    let content_length: usize = get_header(req, CONTENT_LENGTH.as_str())?;
+    let content_length: usize = get_header(req, CONTENT_LENGTH.as_str())?.unwrap(); //todo
     let mut body = BytesMut::with_capacity(content_length);
     let mut payload = req.take_payload();
     while let Some(chunk) = payload.next().await {

From be13d2a572a0a43a07af931fb127bc46d4f58d82 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 17 Feb 2024 17:38:08 +0200
Subject: [PATCH 171/284] 30-access-policies app authorization

---
 src/middleware/authentication/mod.rs | 83 ++++++++++++++++++----------
 src/startup.rs                       |  2 +-
 2 files changed, 54 insertions(+), 31 deletions(-)

diff --git a/src/middleware/authentication/mod.rs b/src/middleware/authentication/mod.rs
index c2a1450e..4a1851e3 100644
--- a/src/middleware/authentication/mod.rs
+++ b/src/middleware/authentication/mod.rs
@@ -73,40 +73,25 @@ where
     fn call(&self, mut req: ServiceRequest) -> Self::Future {
         let service = self.service.clone();
         async move {
-            let client_id: i32 = get_header(&req, "stacker-id")?.unwrap(); //todo
-            let header_hash: String = get_header(&req, "stacker-hash")?.unwrap(); //todo
-
-            let db_pool = req.app_data::<web::Data<Pool<Postgres>>>().unwrap().get_ref();
-            let client: Client = db_fetch_client(db_pool, client_id).await?;
-            if client.secret.is_none() {
-                return Err("client is not active".to_string());
-            }
-
-            let client_secret = client.secret.as_ref().unwrap().as_bytes();
-            let body_hash = compute_body_hash(&mut req, client_secret).await?;
-            if header_hash != body_hash {
-                return Err("hash is wrong".to_string());
-            }
-
-            match req.extensions_mut().insert(Arc::new(client)) {
-                Some(_) => {
-                    tracing::error!("client middleware already called once");
-                    return Err("".to_string());
+            let authorization = get_header::<String>(&req, "authorization")?;
+            let client_id = get_header::<i32>(&req, "stacker-id")?;
+            if authorization.is_some() {
+                //try_authorize_bearer(); //todo
+            } else if client_id.is_some() {
+                try_authorize_id_hash(&mut req, client_id.unwrap()).await?;
+            } else {
+                let accesscontrol_vals = actix_casbin_auth::CasbinVals {
+                    subject: "anonym".to_string(),
+                    domain: None,
+                };
+                if req.extensions_mut().insert(accesscontrol_vals).is_some() {
+                    return Err("sth wrong with access control".to_string());
                 }
-                None => {}
-            }
-
-            let accesscontrol_vals = actix_casbin_auth::CasbinVals {
-                subject: client_id.to_string(),
-                domain: None,
-            };
-            if req.extensions_mut().insert(accesscontrol_vals).is_some() {
-                return Err("sth wrong with access control".to_string());
             }
 
             Ok(req)
         }
-        .then(|req| async move {
+        .then(|req: Result<ServiceRequest, String>| async move {
             match req {
                 Ok(req) => {
                     let service = service.lock().await;
@@ -165,7 +150,7 @@ async fn db_fetch_client(db_pool: &Pool<Postgres>, client_id: i32) -> Result<Cli
 }
 
 async fn compute_body_hash(req: &mut ServiceRequest, client_secret: &[u8]) -> Result<String, String> {
-    let content_length: usize = get_header(req, CONTENT_LENGTH.as_str())?.unwrap(); //todo
+    let content_length: usize = get_header(req, CONTENT_LENGTH.as_str())?.unwrap(); 
     let mut body = BytesMut::with_capacity(content_length);
     let mut payload = req.take_payload();
     while let Some(chunk) = payload.next().await {
@@ -188,3 +173,41 @@ async fn compute_body_hash(req: &mut ServiceRequest, client_secret: &[u8]) -> Re
 
     Ok(format!("{:x}", mac.finalize().into_bytes()))
 }
+
+async fn try_authorize_id_hash(req: &mut ServiceRequest, client_id: i32) -> Result<(), String> {
+    let header_hash = get_header::<String>(&req, "stacker-hash")?; 
+    if header_hash.is_none() {
+        return Err("stacker-hash header is not set".to_string());
+    } //todo
+    let header_hash = header_hash.unwrap();
+
+    let db_pool = req.app_data::<web::Data<Pool<Postgres>>>().unwrap().get_ref();
+    let client: Client = db_fetch_client(db_pool, client_id).await?;
+    if client.secret.is_none() {
+        return Err("client is not active".to_string());
+    }
+
+    let client_secret = client.secret.as_ref().unwrap().as_bytes();
+    let body_hash = compute_body_hash(req, client_secret).await?;
+    if header_hash != body_hash {
+        return Err("hash is wrong".to_string());
+    }
+
+    match req.extensions_mut().insert(Arc::new(client)) {
+        Some(_) => {
+            tracing::error!("client middleware already called once");
+            return Err("".to_string());
+        }
+        None => {}
+    }
+
+    let accesscontrol_vals = actix_casbin_auth::CasbinVals {
+        subject: client_id.to_string(),
+        domain: None,
+    };
+    if req.extensions_mut().insert(accesscontrol_vals).is_some() {
+        return Err("sth wrong with access control".to_string());
+    }
+
+    Ok(())
+}
diff --git a/src/startup.rs b/src/startup.rs
index 83b8548b..279044e6 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -29,8 +29,8 @@ pub async fn run(
     let server = HttpServer::new(move || {
         App::new()
             .wrap(TracingLogger::default())
-            .wrap(middleware::authentication::Manager::new())
             .wrap(authorization.clone()) 
+            .wrap(middleware::authentication::Manager::new())
             .wrap(Cors::permissive())
             .service(
                 web::scope("/health_check").service(routes::health_check)

From be1cb818a35f8743bbddde63dfcb833809441455 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 17 Feb 2024 17:56:54 +0200
Subject: [PATCH 172/284] 30-access-policies two authentication managers in one
 bowl

---
 src/middleware/authentication/mod.rs | 99 ++++++++++++++++++++++------
 src/middleware/mod.rs                |  1 -
 src/middleware/trydirect.rs          | 56 ----------------
 3 files changed, 79 insertions(+), 77 deletions(-)
 delete mode 100644 src/middleware/trydirect.rs

diff --git a/src/middleware/authentication/mod.rs b/src/middleware/authentication/mod.rs
index 4a1851e3..1b79f1c3 100644
--- a/src/middleware/authentication/mod.rs
+++ b/src/middleware/authentication/mod.rs
@@ -1,24 +1,34 @@
-use crate::helpers::JsonResponse;
-use crate::models::Client;
+use crate::{
+    helpers::JsonResponse,
+    models,
+    forms,
+    configuration::Settings,
+};
 use actix_http::header::CONTENT_LENGTH;
-use actix_web::web::BytesMut;
-use actix_web::HttpMessage;
-use futures::future::{FutureExt, LocalBoxFuture};
-use futures::lock::Mutex;
-use futures::task::{Context, Poll};
-use futures::StreamExt;
+use futures::{
+    future::{FutureExt, LocalBoxFuture},
+    lock::Mutex,
+    task::{Context, Poll},
+    StreamExt,
+};
 use hmac::{Hmac, Mac};
 use sha2::Sha256;
-use std::future::{ready, Ready};
-use std::str::FromStr;
-use std::sync::Arc;
+use std::{
+    future::{ready, Ready},
+    str::FromStr,
+    sync::Arc,
+};
 use tracing::Instrument;
-
-use actix_web::{
-    dev::{Service, ServiceRequest, ServiceResponse, Transform},
+use actix_web::{};
+use reqwest::header::{ACCEPT, CONTENT_TYPE};
+use actix_web::{ 
+    web::BytesMut,
+    HttpMessage,
+    web,
+    Error,
+    dev::{ServiceRequest, Service, ServiceResponse, Transform},
     error::ErrorBadRequest,
     http::header::HeaderName,
-    web, Error,
 };
 use sqlx::{Pool, Postgres};
 
@@ -76,7 +86,7 @@ where
             let authorization = get_header::<String>(&req, "authorization")?;
             let client_id = get_header::<i32>(&req, "stacker-id")?;
             if authorization.is_some() {
-                //try_authorize_bearer(); //todo
+                try_authorize_bearer(&mut req, authorization.unwrap()).await?; 
             } else if client_id.is_some() {
                 try_authorize_id_hash(&mut req, client_id.unwrap()).await?;
             } else {
@@ -98,7 +108,7 @@ where
                     service.call(req).await
                 }
                 Err(msg) => Err(ErrorBadRequest(
-                    JsonResponse::<Client>::build().set_msg(msg).to_string(),
+                    JsonResponse::<models::Client>::build().set_msg(msg).to_string(),
                 )),
             }
         })
@@ -127,11 +137,11 @@ where
         .map(|v| Some(v))
 }
 
-async fn db_fetch_client(db_pool: &Pool<Postgres>, client_id: i32) -> Result<Client, String> { //todo
+async fn db_fetch_client(db_pool: &Pool<Postgres>, client_id: i32) -> Result<models::Client, String> { //todo
     let query_span = tracing::info_span!("Fetching the client by ID");
 
     sqlx::query_as!(
-        Client,
+        models::Client,
         r#"SELECT id, user_id, secret FROM client c WHERE c.id = $1"#,
         client_id,
         )
@@ -174,6 +184,7 @@ async fn compute_body_hash(req: &mut ServiceRequest, client_secret: &[u8]) -> Re
     Ok(format!("{:x}", mac.finalize().into_bytes()))
 }
 
+#[tracing::instrument(name = "try authorize. stacker-id header")]
 async fn try_authorize_id_hash(req: &mut ServiceRequest, client_id: i32) -> Result<(), String> {
     let header_hash = get_header::<String>(&req, "stacker-hash")?; 
     if header_hash.is_none() {
@@ -182,7 +193,7 @@ async fn try_authorize_id_hash(req: &mut ServiceRequest, client_id: i32) -> Resu
     let header_hash = header_hash.unwrap();
 
     let db_pool = req.app_data::<web::Data<Pool<Postgres>>>().unwrap().get_ref();
-    let client: Client = db_fetch_client(db_pool, client_id).await?;
+    let client: models::Client = db_fetch_client(db_pool, client_id).await?;
     if client.secret.is_none() {
         return Err("client is not active".to_string());
     }
@@ -211,3 +222,51 @@ async fn try_authorize_id_hash(req: &mut ServiceRequest, client_id: i32) -> Resu
 
     Ok(())
 }
+
+#[tracing::instrument(name = "try authorize. Authorization header")]
+async fn try_authorize_bearer(req: &mut ServiceRequest, authorization: String) -> Result<(), String> {
+    let settings = req.app_data::<web::Data<Settings>>().unwrap();
+    let token = "abc"; //todo
+    let user = match fetch_user(settings.auth_url.as_str(), token).await {
+        Ok(user) => user,
+        Err(err) => {
+            return Err(format!("{}", err));
+        }
+    }; //todo . process the err
+
+    if req.extensions_mut().insert(Arc::new(user)).is_some() {
+        return Err("user already logged".to_string());
+    }
+
+    let accesscontrol_vals = actix_casbin_auth::CasbinVals {
+        subject: String::from("alice"), //todo username or anonymous
+        domain: None,
+    };
+    if req.extensions_mut().insert(accesscontrol_vals).is_some() {
+        return Err("sth wrong with access control".to_string());
+    }
+
+    Ok(())
+}
+
+async fn fetch_user(auth_url: &str, token: &str) -> Result<models::User, String> {
+    let client = reqwest::Client::new();
+    let resp = client
+        .get(auth_url)
+        .bearer_auth(token)
+        .header(CONTENT_TYPE, "application/json")
+        .header(ACCEPT, "application/json")
+        .send()
+        .await
+        .map_err(|_err| "no resp from auth server".to_string())?;
+
+    if !resp.status().is_success() {
+        return Err("401 Unauthorized".to_string());
+    }
+
+    resp
+        .json::<forms::UserForm>()
+        .await
+        .map_err(|_err| "can't parse the response body".to_string())?
+        .try_into()
+}
diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs
index 5efc32ed..6d342506 100644
--- a/src/middleware/mod.rs
+++ b/src/middleware/mod.rs
@@ -1,3 +1,2 @@
 pub mod authentication;
-pub mod trydirect;
 pub mod authorization;
diff --git a/src/middleware/trydirect.rs b/src/middleware/trydirect.rs
deleted file mode 100644
index e9ce5eb8..00000000
--- a/src/middleware/trydirect.rs
+++ /dev/null
@@ -1,56 +0,0 @@
-use crate::{configuration::Settings, helpers::JsonResponse};
-use crate::models;
-use crate::forms;
-use actix_web::{web, dev::ServiceRequest, Error, HttpMessage};
-use actix_web_httpauth::extractors::bearer::BearerAuth;
-use futures::future::{FutureExt};
-use reqwest::header::{ACCEPT, CONTENT_TYPE};
-use std::sync::Arc;
-
-#[tracing::instrument(name = "TryDirect bearer guard.")]
-pub async fn bearer_guard( req: ServiceRequest, credentials: BearerAuth) -> Result<ServiceRequest, (Error, ServiceRequest)> {
-    let settings = req.app_data::<web::Data<Settings>>().unwrap();
-    let token = credentials.token();
-    let user = match fetch_user(settings.auth_url.as_str(), token).await {
-        Ok(user) => user,
-        Err(err) => {
-            return Err((JsonResponse::<i32>::build().unauthorized(err), req)); //todo anonymous
-        }
-    };
-
-    if req.extensions_mut().insert(Arc::new(user)).is_some() {
-        return Err((JsonResponse::<i32>::build().unauthorized("user already logged"), req)); //todo 500 internal error
-    }
-
-    let accesscontrol_vals = actix_casbin_auth::CasbinVals {
-        subject: String::from("alice"), //todo username or anonymous
-        domain: None,
-    };
-    if req.extensions_mut().insert(accesscontrol_vals).is_some() {
-        return Err((JsonResponse::<i32>::build().unauthorized("sth wrong with access control"), req)); //todo 500 internal error
-    }
-
-    Ok(req)
-}
-
-async fn fetch_user(auth_url: &str, token: &str) -> Result<models::User, String> {
-    let client = reqwest::Client::new();
-    let resp = client
-        .get(auth_url)
-        .bearer_auth(token)
-        .header(CONTENT_TYPE, "application/json")
-        .header(ACCEPT, "application/json")
-        .send()
-        .await
-        .map_err(|_err| "no resp from auth server".to_string())?;
-
-    if !resp.status().is_success() {
-        return Err("401 Unauthorized".to_string());
-    }
-
-    resp
-        .json::<forms::UserForm>()
-        .await
-        .map_err(|_err| "can't parse the response body".to_string())?
-        .try_into()
-}

From f0e3df50bfe94d296bde4a827cb231080ccb477c Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sun, 18 Feb 2024 08:25:30 +0200
Subject: [PATCH 173/284] 30-access-policies authorization.rs

---
 src/middleware/authorization.rs | 32 ++++++++++++++++++++++++++++++++
 1 file changed, 32 insertions(+)
 create mode 100644 src/middleware/authorization.rs

diff --git a/src/middleware/authorization.rs b/src/middleware/authorization.rs
new file mode 100644
index 00000000..47601146
--- /dev/null
+++ b/src/middleware/authorization.rs
@@ -0,0 +1,32 @@
+use actix_casbin_auth::{
+    CasbinService,
+    casbin::{
+        DefaultModel,
+        CoreApi,
+        function_map::key_match2
+    }
+};
+use std::io::{Error, ErrorKind};
+use sqlx_adapter::SqlxAdapter;
+
+pub async fn try_new(db_connection_address: String) -> Result<CasbinService, Error> {
+    let m = DefaultModel::from_file("access_control.conf")
+        .await
+        .map_err(|err| Error::new(ErrorKind::Other, format!("{err:?}")))?;
+    let a = SqlxAdapter::new(db_connection_address, 8)
+        .await
+        .map_err(|err| Error::new(ErrorKind::Other, format!("{err:?}")))?;
+
+    let mut casbin_service = CasbinService::new(m, a)
+        .await
+        .map_err(|err| Error::new(ErrorKind::Other, format!("{err:?}")))?;
+
+    casbin_service
+        .write()
+        .await
+        .get_role_manager()
+        .write()
+        .matching_fn(Some(key_match2), None);
+
+    Ok(casbin_service)
+}

From 626baec4f5f098ad59d83cbd99e5d67342606c15 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sun, 18 Feb 2024 08:51:31 +0200
Subject: [PATCH 174/284] 30-access-policies

---
 src/middleware/authentication/manager.rs      | 37 ++++++++
 .../authentication/manager_middleware.rs      | 58 ++++++++++++
 src/middleware/authentication/mod.rs          | 91 ++-----------------
 3 files changed, 102 insertions(+), 84 deletions(-)
 create mode 100644 src/middleware/authentication/manager.rs
 create mode 100644 src/middleware/authentication/manager_middleware.rs

diff --git a/src/middleware/authentication/manager.rs b/src/middleware/authentication/manager.rs
new file mode 100644
index 00000000..2b8e09de
--- /dev/null
+++ b/src/middleware/authentication/manager.rs
@@ -0,0 +1,37 @@
+use crate::middleware::authentication::*;
+
+use std::sync::Arc;
+use std::future::{ready, Ready};
+use futures::lock::Mutex;
+
+use actix_web::{ 
+    Error,
+    dev::{Service, ServiceRequest, ServiceResponse, Transform},
+};
+
+pub struct Manager {}
+
+impl Manager {
+    pub fn new() -> Self {
+        Self {}
+    }
+}
+
+impl<S, B> Transform<S, ServiceRequest> for Manager
+where
+    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
+    S::Future: 'static,
+    B: 'static,
+{
+    type Response = ServiceResponse<B>;
+    type Error = Error;
+    type InitError = ();
+    type Transform = ManagerMiddleware<S>;
+    type Future = Ready<Result<Self::Transform, Self::InitError>>;
+
+    fn new_transform(&self, service: S) -> Self::Future {
+        ready(Ok(ManagerMiddleware {
+            service: Arc::new(Mutex::new(service)),
+        }))
+    }
+}
diff --git a/src/middleware/authentication/manager_middleware.rs b/src/middleware/authentication/manager_middleware.rs
new file mode 100644
index 00000000..67ecbf7d
--- /dev/null
+++ b/src/middleware/authentication/manager_middleware.rs
@@ -0,0 +1,58 @@
+use crate::middleware::authentication::*;
+
+pub struct ManagerMiddleware<S> {
+    pub service: Arc<Mutex<S>>,
+}
+
+impl<S, B> Service<ServiceRequest> for ManagerMiddleware<S>
+where
+    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
+    S::Future: 'static,
+    B: 'static,
+{
+    type Response = ServiceResponse<B>;
+    type Error = S::Error;
+    type Future = LocalBoxFuture<'static, Result<ServiceResponse<B>, Error>>;
+
+    fn poll_ready(&self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
+        self.service
+            .try_lock()
+            .expect("Authentication ManagerMiddleware was called allready")
+            .poll_ready(ctx)
+    }
+
+    fn call(&self, mut req: ServiceRequest) -> Self::Future {
+        let service = self.service.clone();
+        async move {
+            let authorization = get_header::<String>(&req, "authorization")?;
+            let client_id = get_header::<i32>(&req, "stacker-id")?;
+            if authorization.is_some() {
+                try_authorize_bearer(&mut req, authorization.unwrap()).await?; 
+            } else if client_id.is_some() {
+                try_authorize_id_hash(&mut req, client_id.unwrap()).await?;
+            } else {
+                let accesscontrol_vals = actix_casbin_auth::CasbinVals {
+                    subject: "anonym".to_string(),
+                    domain: None,
+                };
+                if req.extensions_mut().insert(accesscontrol_vals).is_some() {
+                    return Err("sth wrong with access control".to_string());
+                }
+            }
+
+            Ok(req)
+        }
+        .then(|req: Result<ServiceRequest, String>| async move {
+            match req {
+                Ok(req) => {
+                    let service = service.lock().await;
+                    service.call(req).await
+                }
+                Err(msg) => Err(ErrorBadRequest(
+                    JsonResponse::<models::Client>::build().set_msg(msg).to_string(),
+                )),
+            }
+        })
+        .boxed_local()
+    }
+}
diff --git a/src/middleware/authentication/mod.rs b/src/middleware/authentication/mod.rs
index 1b79f1c3..36986c56 100644
--- a/src/middleware/authentication/mod.rs
+++ b/src/middleware/authentication/mod.rs
@@ -1,3 +1,9 @@
+mod manager;
+mod manager_middleware;
+
+pub use manager::*;
+pub use manager_middleware::*;
+
 use crate::{
     helpers::JsonResponse,
     models,
@@ -32,90 +38,6 @@ use actix_web::{
 };
 use sqlx::{Pool, Postgres};
 
-pub struct Manager {}
-
-impl Manager {
-    pub fn new() -> Self {
-        Self {}
-    }
-}
-
-impl<S, B> Transform<S, ServiceRequest> for Manager
-where
-    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
-    S::Future: 'static,
-    B: 'static,
-{
-    type Response = ServiceResponse<B>;
-    type Error = Error;
-    type InitError = ();
-    type Transform = ManagerMiddleware<S>;
-    type Future = Ready<Result<Self::Transform, Self::InitError>>;
-
-    fn new_transform(&self, service: S) -> Self::Future {
-        ready(Ok(ManagerMiddleware {
-            service: Arc::new(Mutex::new(service)),
-        }))
-    }
-}
-
-pub struct ManagerMiddleware<S> {
-    service: Arc<Mutex<S>>,
-}
-
-impl<S, B> Service<ServiceRequest> for ManagerMiddleware<S>
-where
-    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
-    S::Future: 'static,
-    B: 'static,
-{
-    type Response = ServiceResponse<B>;
-    type Error = S::Error;
-    type Future = LocalBoxFuture<'static, Result<ServiceResponse<B>, Error>>;
-
-    fn poll_ready(&self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
-        self.service
-            .try_lock()
-            .expect("Authentication ManagerMiddleware was called allready")
-            .poll_ready(ctx)
-    }
-
-    fn call(&self, mut req: ServiceRequest) -> Self::Future {
-        let service = self.service.clone();
-        async move {
-            let authorization = get_header::<String>(&req, "authorization")?;
-            let client_id = get_header::<i32>(&req, "stacker-id")?;
-            if authorization.is_some() {
-                try_authorize_bearer(&mut req, authorization.unwrap()).await?; 
-            } else if client_id.is_some() {
-                try_authorize_id_hash(&mut req, client_id.unwrap()).await?;
-            } else {
-                let accesscontrol_vals = actix_casbin_auth::CasbinVals {
-                    subject: "anonym".to_string(),
-                    domain: None,
-                };
-                if req.extensions_mut().insert(accesscontrol_vals).is_some() {
-                    return Err("sth wrong with access control".to_string());
-                }
-            }
-
-            Ok(req)
-        }
-        .then(|req: Result<ServiceRequest, String>| async move {
-            match req {
-                Ok(req) => {
-                    let service = service.lock().await;
-                    service.call(req).await
-                }
-                Err(msg) => Err(ErrorBadRequest(
-                    JsonResponse::<models::Client>::build().set_msg(msg).to_string(),
-                )),
-            }
-        })
-        .boxed_local()
-    }
-}
-
 fn get_header<T>(req: &ServiceRequest, header_name: &'static str) -> Result<Option<T>, String>
 where
     T: FromStr,
@@ -270,3 +192,4 @@ async fn fetch_user(auth_url: &str, token: &str) -> Result<models::User, String>
         .map_err(|_err| "can't parse the response body".to_string())?
         .try_into()
 }
+

From 1820776afe98d894731ee4779d19d622252a58b3 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Mon, 19 Feb 2024 17:59:02 +0200
Subject: [PATCH 175/284] 30-access-policies refactor

---
 src/middleware/authentication/getheader.rs    |  24 +++
 .../authentication/manager_middleware.rs      |  20 +-
 .../authentication/method/f_hmac.rs           | 102 +++++++++
 .../authentication/method/f_oauth.rs          |  60 ++++++
 src/middleware/authentication/method/mod.rs   |   5 +
 src/middleware/authentication/mod.rs          | 193 +-----------------
 6 files changed, 207 insertions(+), 197 deletions(-)
 create mode 100644 src/middleware/authentication/getheader.rs
 create mode 100644 src/middleware/authentication/method/f_hmac.rs
 create mode 100644 src/middleware/authentication/method/f_oauth.rs
 create mode 100644 src/middleware/authentication/method/mod.rs

diff --git a/src/middleware/authentication/getheader.rs b/src/middleware/authentication/getheader.rs
new file mode 100644
index 00000000..d8104001
--- /dev/null
+++ b/src/middleware/authentication/getheader.rs
@@ -0,0 +1,24 @@
+use actix_web::{ http::header::HeaderName, dev::ServiceRequest};
+use std::str::FromStr;
+
+pub fn get_header<T>(req: &ServiceRequest, header_name: &'static str) -> Result<Option<T>, String>
+where
+    T: FromStr,
+{
+    let header_value = req
+        .headers()
+        .get(HeaderName::from_static(header_name));
+
+    if header_value.is_none() {
+        return Ok(None);
+    }
+
+    header_value
+        .unwrap()
+        .to_str()
+        .map_err(|_| format!("header {header_name} can't be converted to string"))? 
+        .parse::<T>()
+        .map_err(|_| format!("header {header_name} has wrong type"))
+        .map(|v| Some(v))
+}
+
diff --git a/src/middleware/authentication/manager_middleware.rs b/src/middleware/authentication/manager_middleware.rs
index 67ecbf7d..8faa9db7 100644
--- a/src/middleware/authentication/manager_middleware.rs
+++ b/src/middleware/authentication/manager_middleware.rs
@@ -1,4 +1,9 @@
 use crate::middleware::authentication::*;
+use actix_web::{error::ErrorBadRequest, HttpMessage, Error, dev::{ServiceRequest, ServiceResponse, Service}};
+use crate::helpers::JsonResponse;
+use futures::{task::{Poll, Context}, future::{FutureExt, LocalBoxFuture}, lock::Mutex};
+use crate::models;
+use std::sync::Arc;
 
 pub struct ManagerMiddleware<S> {
     pub service: Arc<Mutex<S>>,
@@ -24,13 +29,14 @@ where
     fn call(&self, mut req: ServiceRequest) -> Self::Future {
         let service = self.service.clone();
         async move {
-            let authorization = get_header::<String>(&req, "authorization")?;
-            let client_id = get_header::<i32>(&req, "stacker-id")?;
-            if authorization.is_some() {
-                try_authorize_bearer(&mut req, authorization.unwrap()).await?; 
-            } else if client_id.is_some() {
-                try_authorize_id_hash(&mut req, client_id.unwrap()).await?;
-            } else {
+            /*
+            method::try_oauth(&mut req).await?
+            || method::try_hmac(&mut req).await?
+            || method::anonym(&mut req); //todo
+            */
+
+            if (!(method::try_oauth(&mut req).await? || method::try_hmac(&mut req).await?)) 
+            {
                 let accesscontrol_vals = actix_casbin_auth::CasbinVals {
                     subject: "anonym".to_string(),
                     domain: None,
diff --git a/src/middleware/authentication/method/f_hmac.rs b/src/middleware/authentication/method/f_hmac.rs
new file mode 100644
index 00000000..998a237f
--- /dev/null
+++ b/src/middleware/authentication/method/f_hmac.rs
@@ -0,0 +1,102 @@
+use hmac::{Hmac, Mac};
+use sha2::Sha256;
+use sqlx::{Pool, Postgres};
+use tracing::Instrument;
+use std::sync::Arc;
+use crate::models;
+use actix_web::{web, dev::ServiceRequest, HttpMessage};
+use crate::middleware::authentication::get_header; //todo move to helpers
+use actix_http::header::CONTENT_LENGTH;
+use futures::StreamExt;
+
+async fn db_fetch_client(db_pool: &Pool<Postgres>, client_id: i32) -> Result<models::Client, String> { //todo
+    let query_span = tracing::info_span!("Fetching the client by ID");
+
+    sqlx::query_as!(
+        models::Client,
+        r#"SELECT id, user_id, secret FROM client c WHERE c.id = $1"#,
+        client_id,
+        )
+        .fetch_one(db_pool)
+        .instrument(query_span)
+        .await
+        .map_err(|err| {
+            match err {
+                sqlx::Error::RowNotFound => "the client is not found".to_string(),
+                e => {
+                    tracing::error!("Failed to execute fetch query: {:?}", e);
+                    String::new()
+                }
+            }
+        })
+}
+
+async fn compute_body_hash(req: &mut ServiceRequest, client_secret: &[u8]) -> Result<String, String> {
+    let content_length: usize = get_header(req, CONTENT_LENGTH.as_str())?.unwrap(); 
+    let mut body = web::BytesMut::with_capacity(content_length);
+    let mut payload = req.take_payload();
+    while let Some(chunk) = payload.next().await {
+        body.extend_from_slice(&chunk.expect("can't unwrap the chunk"));
+    }
+
+    let mut mac =
+        match Hmac::<Sha256>::new_from_slice(client_secret) {
+            Ok(mac) => mac,
+            Err(err) => {
+                tracing::error!("error generating hmac {err:?}");
+                return Err("".to_string());
+            }
+        };
+
+    mac.update(body.as_ref());
+    let (_, mut payload) = actix_http::h1::Payload::create(true);
+    payload.unread_data(body.into());
+    req.set_payload(payload.into());
+
+    Ok(format!("{:x}", mac.finalize().into_bytes()))
+}
+
+#[tracing::instrument(name = "try authorize. stacker-id header")]
+pub async fn try_hmac(req: &mut ServiceRequest) -> Result<bool, String> {
+    let client_id = get_header::<i32>(&req, "stacker-id")?;
+    if client_id.is_none() {
+        return Ok(false);
+    }
+    let client_id = client_id.unwrap();
+
+    let header_hash = get_header::<String>(&req, "stacker-hash")?; 
+    if header_hash.is_none() {
+        return Err("stacker-hash header is not set".to_string());
+    } //todo
+    let header_hash = header_hash.unwrap();
+
+    let db_pool = req.app_data::<web::Data<Pool<Postgres>>>().unwrap().get_ref();
+    let client: models::Client = db_fetch_client(db_pool, client_id).await?;
+    if client.secret.is_none() {
+        return Err("client is not active".to_string());
+    }
+
+    let client_secret = client.secret.as_ref().unwrap().as_bytes();
+    let body_hash = compute_body_hash(req, client_secret).await?;
+    if header_hash != body_hash {
+        return Err("hash is wrong".to_string());
+    }
+
+    match req.extensions_mut().insert(Arc::new(client)) {
+        Some(_) => {
+            tracing::error!("client middleware already called once");
+            return Err("".to_string());
+        }
+        None => {}
+    }
+
+    let accesscontrol_vals = actix_casbin_auth::CasbinVals {
+        subject: client_id.to_string(),
+        domain: None,
+    };
+    if req.extensions_mut().insert(accesscontrol_vals).is_some() {
+        return Err("sth wrong with access control".to_string());
+    }
+
+    Ok(true)
+}
diff --git a/src/middleware/authentication/method/f_oauth.rs b/src/middleware/authentication/method/f_oauth.rs
new file mode 100644
index 00000000..02b20243
--- /dev/null
+++ b/src/middleware/authentication/method/f_oauth.rs
@@ -0,0 +1,60 @@
+use crate::middleware::authentication::get_header;
+use actix_web::{web, dev::{ServiceRequest}, HttpMessage};
+use crate::configuration::Settings;
+use crate::models;
+use crate::forms;
+use reqwest::header::{ACCEPT, CONTENT_TYPE};
+use std::sync::Arc;
+
+#[tracing::instrument(name = "try authorize. Authorization header")]
+pub async fn try_oauth(req: &mut ServiceRequest) -> Result<bool, String> {
+    let authentication = get_header::<String>(&req, "authorization")?; //todo
+    if authentication.is_none() {
+        return Ok(false);
+    }
+
+    let settings = req.app_data::<web::Data<Settings>>().unwrap();
+    let token = "abc"; //todo
+    let user = match fetch_user(settings.auth_url.as_str(), token).await {
+        Ok(user) => user,
+        Err(err) => {
+            return Err(format!("{}", err));
+        }
+    }; //todo . process the err
+
+    if req.extensions_mut().insert(Arc::new(user)).is_some() {
+        return Err("user already logged".to_string());
+    }
+
+    let accesscontrol_vals = actix_casbin_auth::CasbinVals {
+        subject: String::from("alice"), //todo username or anonymous
+        domain: None,
+    };
+    if req.extensions_mut().insert(accesscontrol_vals).is_some() {
+        return Err("sth wrong with access control".to_string());
+    }
+
+    Ok(true)
+}
+
+async fn fetch_user(auth_url: &str, token: &str) -> Result<models::User, String> {
+    let client = reqwest::Client::new();
+    let resp = client
+        .get(auth_url)
+        .bearer_auth(token)
+        .header(CONTENT_TYPE, "application/json")
+        .header(ACCEPT, "application/json")
+        .send()
+        .await
+        .map_err(|_err| "no resp from auth server".to_string())?;
+
+    if !resp.status().is_success() {
+        return Err("401 Unauthorized".to_string());
+    }
+
+    resp
+        .json::<forms::UserForm>()
+        .await
+        .map_err(|_err| "can't parse the response body".to_string())?
+        .try_into()
+}
diff --git a/src/middleware/authentication/method/mod.rs b/src/middleware/authentication/method/mod.rs
new file mode 100644
index 00000000..5d82a318
--- /dev/null
+++ b/src/middleware/authentication/method/mod.rs
@@ -0,0 +1,5 @@
+mod f_oauth;
+mod f_hmac;
+
+pub use f_oauth::try_oauth;
+pub use f_hmac::try_hmac;
diff --git a/src/middleware/authentication/mod.rs b/src/middleware/authentication/mod.rs
index 36986c56..5338d6dd 100644
--- a/src/middleware/authentication/mod.rs
+++ b/src/middleware/authentication/mod.rs
@@ -1,195 +1,8 @@
+mod getheader;
 mod manager;
 mod manager_middleware;
+mod method;
 
+pub use getheader::*;
 pub use manager::*;
 pub use manager_middleware::*;
-
-use crate::{
-    helpers::JsonResponse,
-    models,
-    forms,
-    configuration::Settings,
-};
-use actix_http::header::CONTENT_LENGTH;
-use futures::{
-    future::{FutureExt, LocalBoxFuture},
-    lock::Mutex,
-    task::{Context, Poll},
-    StreamExt,
-};
-use hmac::{Hmac, Mac};
-use sha2::Sha256;
-use std::{
-    future::{ready, Ready},
-    str::FromStr,
-    sync::Arc,
-};
-use tracing::Instrument;
-use actix_web::{};
-use reqwest::header::{ACCEPT, CONTENT_TYPE};
-use actix_web::{ 
-    web::BytesMut,
-    HttpMessage,
-    web,
-    Error,
-    dev::{ServiceRequest, Service, ServiceResponse, Transform},
-    error::ErrorBadRequest,
-    http::header::HeaderName,
-};
-use sqlx::{Pool, Postgres};
-
-fn get_header<T>(req: &ServiceRequest, header_name: &'static str) -> Result<Option<T>, String>
-where
-    T: FromStr,
-{
-    let header_value = req
-        .headers()
-        .get(HeaderName::from_static(header_name));
-
-    if header_value.is_none() {
-        return Ok(None);
-    }
-
-    header_value
-        .unwrap()
-        .to_str()
-        .map_err(|_| format!("header {header_name} can't be converted to string"))? 
-        .parse::<T>()
-        .map_err(|_| format!("header {header_name} has wrong type"))
-        .map(|v| Some(v))
-}
-
-async fn db_fetch_client(db_pool: &Pool<Postgres>, client_id: i32) -> Result<models::Client, String> { //todo
-    let query_span = tracing::info_span!("Fetching the client by ID");
-
-    sqlx::query_as!(
-        models::Client,
-        r#"SELECT id, user_id, secret FROM client c WHERE c.id = $1"#,
-        client_id,
-        )
-        .fetch_one(db_pool)
-        .instrument(query_span)
-        .await
-        .map_err(|err| {
-            match err {
-                sqlx::Error::RowNotFound => "the client is not found".to_string(),
-                e => {
-                    tracing::error!("Failed to execute fetch query: {:?}", e);
-                    String::new()
-                }
-            }
-        })
-}
-
-async fn compute_body_hash(req: &mut ServiceRequest, client_secret: &[u8]) -> Result<String, String> {
-    let content_length: usize = get_header(req, CONTENT_LENGTH.as_str())?.unwrap(); 
-    let mut body = BytesMut::with_capacity(content_length);
-    let mut payload = req.take_payload();
-    while let Some(chunk) = payload.next().await {
-        body.extend_from_slice(&chunk.expect("can't unwrap the chunk"));
-    }
-
-    let mut mac =
-        match Hmac::<Sha256>::new_from_slice(client_secret) {
-            Ok(mac) => mac,
-            Err(err) => {
-                tracing::error!("error generating hmac {err:?}");
-                return Err("".to_string());
-            }
-        };
-
-    mac.update(body.as_ref());
-    let (_, mut payload) = actix_http::h1::Payload::create(true);
-    payload.unread_data(body.into());
-    req.set_payload(payload.into());
-
-    Ok(format!("{:x}", mac.finalize().into_bytes()))
-}
-
-#[tracing::instrument(name = "try authorize. stacker-id header")]
-async fn try_authorize_id_hash(req: &mut ServiceRequest, client_id: i32) -> Result<(), String> {
-    let header_hash = get_header::<String>(&req, "stacker-hash")?; 
-    if header_hash.is_none() {
-        return Err("stacker-hash header is not set".to_string());
-    } //todo
-    let header_hash = header_hash.unwrap();
-
-    let db_pool = req.app_data::<web::Data<Pool<Postgres>>>().unwrap().get_ref();
-    let client: models::Client = db_fetch_client(db_pool, client_id).await?;
-    if client.secret.is_none() {
-        return Err("client is not active".to_string());
-    }
-
-    let client_secret = client.secret.as_ref().unwrap().as_bytes();
-    let body_hash = compute_body_hash(req, client_secret).await?;
-    if header_hash != body_hash {
-        return Err("hash is wrong".to_string());
-    }
-
-    match req.extensions_mut().insert(Arc::new(client)) {
-        Some(_) => {
-            tracing::error!("client middleware already called once");
-            return Err("".to_string());
-        }
-        None => {}
-    }
-
-    let accesscontrol_vals = actix_casbin_auth::CasbinVals {
-        subject: client_id.to_string(),
-        domain: None,
-    };
-    if req.extensions_mut().insert(accesscontrol_vals).is_some() {
-        return Err("sth wrong with access control".to_string());
-    }
-
-    Ok(())
-}
-
-#[tracing::instrument(name = "try authorize. Authorization header")]
-async fn try_authorize_bearer(req: &mut ServiceRequest, authorization: String) -> Result<(), String> {
-    let settings = req.app_data::<web::Data<Settings>>().unwrap();
-    let token = "abc"; //todo
-    let user = match fetch_user(settings.auth_url.as_str(), token).await {
-        Ok(user) => user,
-        Err(err) => {
-            return Err(format!("{}", err));
-        }
-    }; //todo . process the err
-
-    if req.extensions_mut().insert(Arc::new(user)).is_some() {
-        return Err("user already logged".to_string());
-    }
-
-    let accesscontrol_vals = actix_casbin_auth::CasbinVals {
-        subject: String::from("alice"), //todo username or anonymous
-        domain: None,
-    };
-    if req.extensions_mut().insert(accesscontrol_vals).is_some() {
-        return Err("sth wrong with access control".to_string());
-    }
-
-    Ok(())
-}
-
-async fn fetch_user(auth_url: &str, token: &str) -> Result<models::User, String> {
-    let client = reqwest::Client::new();
-    let resp = client
-        .get(auth_url)
-        .bearer_auth(token)
-        .header(CONTENT_TYPE, "application/json")
-        .header(ACCEPT, "application/json")
-        .send()
-        .await
-        .map_err(|_err| "no resp from auth server".to_string())?;
-
-    if !resp.status().is_success() {
-        return Err("401 Unauthorized".to_string());
-    }
-
-    resp
-        .json::<forms::UserForm>()
-        .await
-        .map_err(|_err| "can't parse the response body".to_string())?
-        .try_into()
-}
-

From 5330ad931ecc37b056539d50ab12447cae404f66 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Tue, 20 Feb 2024 07:40:33 +0200
Subject: [PATCH 176/284] 30-access-policies anonym logic separated

---
 .../authentication/manager_middleware.rs          | 15 +--------------
 src/middleware/authentication/method/f_anonym.rs  | 15 +++++++++++++++
 src/middleware/authentication/method/f_hmac.rs    |  2 +-
 src/middleware/authentication/method/f_oauth.rs   |  2 +-
 src/middleware/authentication/method/mod.rs       |  2 ++
 5 files changed, 20 insertions(+), 16 deletions(-)
 create mode 100644 src/middleware/authentication/method/f_anonym.rs

diff --git a/src/middleware/authentication/manager_middleware.rs b/src/middleware/authentication/manager_middleware.rs
index 8faa9db7..0a72c8d3 100644
--- a/src/middleware/authentication/manager_middleware.rs
+++ b/src/middleware/authentication/manager_middleware.rs
@@ -29,22 +29,9 @@ where
     fn call(&self, mut req: ServiceRequest) -> Self::Future {
         let service = self.service.clone();
         async move {
-            /*
             method::try_oauth(&mut req).await?
             || method::try_hmac(&mut req).await?
-            || method::anonym(&mut req); //todo
-            */
-
-            if (!(method::try_oauth(&mut req).await? || method::try_hmac(&mut req).await?)) 
-            {
-                let accesscontrol_vals = actix_casbin_auth::CasbinVals {
-                    subject: "anonym".to_string(),
-                    domain: None,
-                };
-                if req.extensions_mut().insert(accesscontrol_vals).is_some() {
-                    return Err("sth wrong with access control".to_string());
-                }
-            }
+            || method::anonym(&mut req)?;
 
             Ok(req)
         }
diff --git a/src/middleware/authentication/method/f_anonym.rs b/src/middleware/authentication/method/f_anonym.rs
new file mode 100644
index 00000000..fa7c2888
--- /dev/null
+++ b/src/middleware/authentication/method/f_anonym.rs
@@ -0,0 +1,15 @@
+use actix_web::dev::ServiceRequest;
+use actix_web::HttpMessage;
+
+#[tracing::instrument(name = "authenticate as anonym")]
+pub fn anonym(req: &mut ServiceRequest) -> Result<bool, String> {
+    let accesscontrol_vals = actix_casbin_auth::CasbinVals {
+        subject: "anonym".to_string(),
+        domain: None,
+    };
+    if req.extensions_mut().insert(accesscontrol_vals).is_some() {
+        return Err("sth wrong with access control".to_string());
+    }
+
+    Ok(true)
+}
diff --git a/src/middleware/authentication/method/f_hmac.rs b/src/middleware/authentication/method/f_hmac.rs
index 998a237f..e385f8f7 100644
--- a/src/middleware/authentication/method/f_hmac.rs
+++ b/src/middleware/authentication/method/f_hmac.rs
@@ -56,7 +56,7 @@ async fn compute_body_hash(req: &mut ServiceRequest, client_secret: &[u8]) -> Re
     Ok(format!("{:x}", mac.finalize().into_bytes()))
 }
 
-#[tracing::instrument(name = "try authorize. stacker-id header")]
+#[tracing::instrument(name = "try authenticate via hmac")]
 pub async fn try_hmac(req: &mut ServiceRequest) -> Result<bool, String> {
     let client_id = get_header::<i32>(&req, "stacker-id")?;
     if client_id.is_none() {
diff --git a/src/middleware/authentication/method/f_oauth.rs b/src/middleware/authentication/method/f_oauth.rs
index 02b20243..5566f49b 100644
--- a/src/middleware/authentication/method/f_oauth.rs
+++ b/src/middleware/authentication/method/f_oauth.rs
@@ -6,7 +6,7 @@ use crate::forms;
 use reqwest::header::{ACCEPT, CONTENT_TYPE};
 use std::sync::Arc;
 
-#[tracing::instrument(name = "try authorize. Authorization header")]
+#[tracing::instrument(name = "try authenticate via bearer")]
 pub async fn try_oauth(req: &mut ServiceRequest) -> Result<bool, String> {
     let authentication = get_header::<String>(&req, "authorization")?; //todo
     if authentication.is_none() {
diff --git a/src/middleware/authentication/method/mod.rs b/src/middleware/authentication/method/mod.rs
index 5d82a318..3d55881a 100644
--- a/src/middleware/authentication/method/mod.rs
+++ b/src/middleware/authentication/method/mod.rs
@@ -1,5 +1,7 @@
 mod f_oauth;
+mod f_anonym;
 mod f_hmac;
 
 pub use f_oauth::try_oauth;
+pub use f_anonym::anonym;
 pub use f_hmac::try_hmac;

From 44c28efbe555b35bfe6976947aa0cc68412322e0 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Tue, 20 Feb 2024 17:18:04 +0200
Subject: [PATCH 177/284] 30-access-policies removed http authentication

---
 Cargo.lock                                    | 219 ++++++++----------
 Cargo.toml                                    |   1 -
 .../authentication/manager_middleware.rs      |   2 +-
 .../authentication/method/f_oauth.rs          |   1 +
 src/startup.rs                                |   1 -
 5 files changed, 104 insertions(+), 120 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 269e5798..da5e64ea 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -56,7 +56,7 @@ dependencies = [
  "actix-rt",
  "actix-service",
  "actix-utils",
- "ahash 0.8.7",
+ "ahash 0.8.9",
  "base64 0.21.7",
  "bitflags 2.4.2",
  "brotli",
@@ -92,7 +92,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb"
 dependencies = [
  "quote",
- "syn 2.0.48",
+ "syn 2.0.50",
 ]
 
 [[package]]
@@ -171,7 +171,7 @@ dependencies = [
  "actix-service",
  "actix-utils",
  "actix-web-codegen",
- "ahash 0.8.7",
+ "ahash 0.8.9",
  "bytes",
  "bytestring",
  "cfg-if",
@@ -205,22 +205,7 @@ dependencies = [
  "actix-router",
  "proc-macro2",
  "quote",
- "syn 2.0.48",
-]
-
-[[package]]
-name = "actix-web-httpauth"
-version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d613edf08a42ccc6864c941d30fe14e1b676a77d16f1dbadc1174d065a0a775"
-dependencies = [
- "actix-utils",
- "actix-web",
- "base64 0.21.7",
- "futures-core",
- "futures-util",
- "log",
- "pin-project-lite",
+ "syn 2.0.50",
 ]
 
 [[package]]
@@ -240,9 +225,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
 
 [[package]]
 name = "ahash"
-version = "0.7.7"
+version = "0.7.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd"
+checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
 dependencies = [
  "getrandom 0.2.12",
  "once_cell",
@@ -251,9 +236,9 @@ dependencies = [
 
 [[package]]
 name = "ahash"
-version = "0.8.7"
+version = "0.8.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01"
+checksum = "d713b3834d76b85304d4d525563c1276e2e30dc97cc67bfb4585a4a29fc2c89f"
 dependencies = [
  "cfg-if",
  "const-random",
@@ -358,9 +343,9 @@ dependencies = [
 
 [[package]]
 name = "anstream"
-version = "0.6.11"
+version = "0.6.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5"
+checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540"
 dependencies = [
  "anstyle",
  "anstyle-parse",
@@ -406,9 +391,9 @@ dependencies = [
 
 [[package]]
 name = "anyhow"
-version = "1.0.79"
+version = "1.0.80"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
+checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1"
 
 [[package]]
 name = "assert-json-diff"
@@ -438,7 +423,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3"
 dependencies = [
  "concurrent-queue",
- "event-listener 5.0.0",
+ "event-listener 5.1.0",
  "event-listener-strategy 0.5.0",
  "futures-core",
  "pin-project-lite",
@@ -516,7 +501,7 @@ dependencies = [
  "futures-io",
  "futures-lite 2.2.0",
  "parking",
- "polling 3.4.0",
+ "polling 3.5.0",
  "rustix 0.38.31",
  "slab",
  "tracing",
@@ -569,7 +554,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.48",
+ "syn 2.0.50",
 ]
 
 [[package]]
@@ -708,9 +693,9 @@ dependencies = [
 
 [[package]]
 name = "bumpalo"
-version = "3.14.0"
+version = "3.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
+checksum = "d32a994c2b3ca201d9b263612a374263f05e7adde37c4707f693dcd375076d1f"
 
 [[package]]
 name = "bytecount"
@@ -844,9 +829,9 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "4.5.0"
+version = "4.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f"
+checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da"
 dependencies = [
  "clap_builder",
  "clap_derive",
@@ -854,9 +839,9 @@ dependencies = [
 
 [[package]]
 name = "clap_builder"
-version = "4.5.0"
+version = "4.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "458bf1f341769dfcf849846f65dffdf9146daa56bcd2a47cb4e1de9915567c99"
+checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb"
 dependencies = [
  "anstream",
  "anstyle",
@@ -873,7 +858,7 @@ dependencies = [
  "heck",
  "proc-macro2",
  "quote",
- "syn 2.0.48",
+ "syn 2.0.50",
 ]
 
 [[package]]
@@ -1001,9 +986,9 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
 
 [[package]]
 name = "crc32fast"
-version = "1.3.2"
+version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
 dependencies = [
  "cfg-if",
 ]
@@ -1161,11 +1146,11 @@ dependencies = [
 
 [[package]]
 name = "derive_builder"
-version = "0.13.0"
+version = "0.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "660047478bc508c0fde22c868991eec0c40a63e48d610befef466d48e2bee574"
+checksum = "8f59169f400d8087f238c5c0c7db6a28af18681717f3b623227d92f397e938c7"
 dependencies = [
- "derive_builder_macro 0.13.0",
+ "derive_builder_macro 0.13.1",
 ]
 
 [[package]]
@@ -1182,9 +1167,9 @@ dependencies = [
 
 [[package]]
 name = "derive_builder_core"
-version = "0.13.0"
+version = "0.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b217e6dd1011a54d12f3b920a411b5abd44b1716ecfe94f5f2f2f7b52e08ab7"
+checksum = "a4ec317cc3e7ef0928b0ca6e4a634a4d6c001672ae210438cf114a83e56b018d"
 dependencies = [
  "darling",
  "proc-macro2",
@@ -1204,11 +1189,11 @@ dependencies = [
 
 [[package]]
 name = "derive_builder_macro"
-version = "0.13.0"
+version = "0.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a5f77d7e20ac9153428f7ca14a88aba652adfc7a0ef0a06d654386310ef663b"
+checksum = "870368c3fb35b8031abb378861d4460f573b92238ec2152c927a21f77e3e0127"
 dependencies = [
- "derive_builder_core 0.13.0",
+ "derive_builder_core 0.13.1",
  "syn 1.0.109",
 ]
 
@@ -1283,8 +1268,8 @@ version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6e366950e7785fe0e404901a2b4e5c09ebd6656767f0c2167e34c5068ce0cc2d"
 dependencies = [
- "derive_builder 0.13.0",
- "indexmap 2.2.2",
+ "derive_builder 0.13.1",
+ "indexmap 2.2.3",
  "serde",
  "serde_yaml",
 ]
@@ -1374,9 +1359,9 @@ dependencies = [
 
 [[package]]
 name = "event-listener"
-version = "5.0.0"
+version = "5.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b72557800024fabbaa2449dd4bf24e37b93702d457a4d4f2b0dd1f0f039f20c1"
+checksum = "b7ad6fd685ce13acd6d9541a30f6db6567a7a24c9ffd4ba2955d29e3f22c8b27"
 dependencies = [
  "concurrent-queue",
  "parking",
@@ -1399,7 +1384,7 @@ version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291"
 dependencies = [
- "event-listener 5.0.0",
+ "event-listener 5.1.0",
  "pin-project-lite",
 ]
 
@@ -1606,7 +1591,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.48",
+ "syn 2.0.50",
 ]
 
 [[package]]
@@ -1713,7 +1698,7 @@ dependencies = [
  "futures-sink",
  "futures-util",
  "http",
- "indexmap 2.2.2",
+ "indexmap 2.2.3",
  "slab",
  "tokio",
  "tokio-util",
@@ -1726,7 +1711,7 @@ version = "0.11.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
 dependencies = [
- "ahash 0.7.7",
+ "ahash 0.7.8",
 ]
 
 [[package]]
@@ -1735,7 +1720,7 @@ version = "0.12.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
 dependencies = [
- "ahash 0.7.7",
+ "ahash 0.7.8",
 ]
 
 [[package]]
@@ -1744,7 +1729,7 @@ version = "0.14.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
 dependencies = [
- "ahash 0.8.7",
+ "ahash 0.8.9",
  "allocator-api2",
 ]
 
@@ -1768,9 +1753,9 @@ dependencies = [
 
 [[package]]
 name = "hermit-abi"
-version = "0.3.5"
+version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3"
+checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd"
 
 [[package]]
 name = "hex"
@@ -1949,9 +1934,9 @@ dependencies = [
 
 [[package]]
 name = "indexmap"
-version = "2.2.2"
+version = "2.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520"
+checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177"
 dependencies = [
  "equivalent",
  "hashbrown 0.14.3",
@@ -2326,9 +2311,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
 
 [[package]]
 name = "openssl"
-version = "0.10.63"
+version = "0.10.64"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8"
+checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f"
 dependencies = [
  "bitflags 2.4.2",
  "cfg-if",
@@ -2347,7 +2332,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.48",
+ "syn 2.0.50",
 ]
 
 [[package]]
@@ -2358,9 +2343,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
 
 [[package]]
 name = "openssl-sys"
-version = "0.9.99"
+version = "0.9.100"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae"
+checksum = "ae94056a791d0e1217d18b6cbdccb02c61e3054fc69893607f4067e3bb0b1fd1"
 dependencies = [
  "cc",
  "libc",
@@ -2504,7 +2489,7 @@ dependencies = [
  "pest_meta",
  "proc-macro2",
  "quote",
- "syn 2.0.48",
+ "syn 2.0.50",
 ]
 
 [[package]]
@@ -2525,7 +2510,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
 dependencies = [
  "fixedbitset",
- "indexmap 2.2.2",
+ "indexmap 2.2.3",
 ]
 
 [[package]]
@@ -2545,7 +2530,7 @@ checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.48",
+ "syn 2.0.50",
 ]
 
 [[package]]
@@ -2585,9 +2570,9 @@ dependencies = [
 
 [[package]]
 name = "pkg-config"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb"
+checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
 
 [[package]]
 name = "polling"
@@ -2607,9 +2592,9 @@ dependencies = [
 
 [[package]]
 name = "polling"
-version = "3.4.0"
+version = "3.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30054e72317ab98eddd8561db0f6524df3367636884b7b21b703e4b280a84a14"
+checksum = "24f040dee2588b4963afb4e420540439d126f73fdacf4a9c486a96d840bac3c9"
 dependencies = [
  "cfg-if",
  "concurrent-queue",
@@ -2900,7 +2885,7 @@ version = "1.17.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f6273372244d04a8a4b0bec080ea1e710403e88c5d9d83f9808b2bfa64f0982a"
 dependencies = [
- "ahash 0.8.7",
+ "ahash 0.8.9",
  "bitflags 2.4.2",
  "instant",
  "num-traits",
@@ -2920,7 +2905,7 @@ checksum = "9db7f8dc4c9d48183a17ce550574c42995252b82d267eaca3fcd1b979159856c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.48",
+ "syn 2.0.50",
 ]
 
 [[package]]
@@ -2940,16 +2925,17 @@ dependencies = [
 
 [[package]]
 name = "ring"
-version = "0.17.7"
+version = "0.17.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74"
+checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
 dependencies = [
  "cc",
+ "cfg-if",
  "getrandom 0.2.12",
  "libc",
  "spin 0.9.8",
  "untrusted 0.9.0",
- "windows-sys 0.48.0",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
@@ -2958,7 +2944,7 @@ version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "98f2771d255fd99f0294f13249fecd0cae6e074f86b4197ec1f1689d537b44d3"
 dependencies = [
- "ahash 0.7.7",
+ "ahash 0.7.8",
  "hashbrown 0.11.2",
 ]
 
@@ -3044,7 +3030,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba"
 dependencies = [
  "log",
- "ring 0.17.7",
+ "ring 0.17.8",
  "rustls-webpki",
  "sct",
 ]
@@ -3088,15 +3074,15 @@ version = "0.101.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
 dependencies = [
- "ring 0.17.7",
+ "ring 0.17.8",
  "untrusted 0.9.0",
 ]
 
 [[package]]
 name = "ryu"
-version = "1.0.16"
+version = "1.0.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
+checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
 
 [[package]]
 name = "same-file"
@@ -3128,7 +3114,7 @@ version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
 dependencies = [
- "ring 0.17.7",
+ "ring 0.17.8",
  "untrusted 0.9.0",
 ]
 
@@ -3157,38 +3143,38 @@ dependencies = [
 
 [[package]]
 name = "semver"
-version = "1.0.21"
+version = "1.0.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
+checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
 dependencies = [
  "serde",
 ]
 
 [[package]]
 name = "serde"
-version = "1.0.196"
+version = "1.0.197"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
+checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.196"
+version = "1.0.197"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
+checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.48",
+ "syn 2.0.50",
 ]
 
 [[package]]
 name = "serde_json"
-version = "1.0.113"
+version = "1.0.114"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
+checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
 dependencies = [
  "itoa",
  "ryu",
@@ -3274,11 +3260,11 @@ dependencies = [
 
 [[package]]
 name = "serde_yaml"
-version = "0.9.31"
+version = "0.9.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "adf8a49373e98a4c5f0ceb5d05aa7c648d75f63774981ed95b7c7443bbd50c6e"
+checksum = "8fd075d994154d4a774f95b51fb96bdc2832b0ea48425c92546073816cda1f2f"
 dependencies = [
- "indexmap 2.2.2",
+ "indexmap 2.2.3",
  "itoa",
  "ryu",
  "serde",
@@ -3439,9 +3425,9 @@ dependencies = [
 
 [[package]]
 name = "sqlx-adapter"
-version = "1.0.0"
+version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e39a25e18b8f9cd4f61b1653be67260d28dcd31b0bd8d3a08ed3ecbe2afa3b84"
+checksum = "24fe30f13db95ad25c9b7480d9a3c8b1fabe42a9ebd670428fa56a7173281eaa"
 dependencies = [
  "async-trait",
  "casbin",
@@ -3455,7 +3441,7 @@ version = "0.6.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fa8241483a83a3f33aa5fff7e7d9def398ff9990b2752b6c6112b83c6d246029"
 dependencies = [
- "ahash 0.7.7",
+ "ahash 0.7.8",
  "atoi 1.0.0",
  "base64 0.13.1",
  "bitflags 1.3.2",
@@ -3510,7 +3496,7 @@ version = "0.7.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d84b0a3c3739e220d94b3239fd69fb1f74bc36e16643423bd99de3b43c21bfbd"
 dependencies = [
- "ahash 0.8.7",
+ "ahash 0.8.9",
  "atoi 2.0.0",
  "byteorder",
  "bytes",
@@ -3526,7 +3512,7 @@ dependencies = [
  "futures-util",
  "hashlink",
  "hex",
- "indexmap 2.2.2",
+ "indexmap 2.2.3",
  "log",
  "memchr",
  "native-tls",
@@ -3663,7 +3649,6 @@ dependencies = [
  "actix-cors",
  "actix-http",
  "actix-web",
- "actix-web-httpauth",
  "chrono",
  "clap",
  "config",
@@ -3675,7 +3660,7 @@ dependencies = [
  "futures-util",
  "glob",
  "hmac",
- "indexmap 2.2.2",
+ "indexmap 2.2.3",
  "lapin",
  "rand 0.8.5",
  "regex",
@@ -3749,9 +3734,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.48"
+version = "2.0.50"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
+checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -3826,22 +3811,22 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "1.0.56"
+version = "1.0.57"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
+checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.56"
+version = "1.0.57"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
+checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.48",
+ "syn 2.0.50",
 ]
 
 [[package]]
@@ -3958,7 +3943,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.48",
+ "syn 2.0.50",
 ]
 
 [[package]]
@@ -4055,7 +4040,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.48",
+ "syn 2.0.50",
 ]
 
 [[package]]
@@ -4064,7 +4049,7 @@ version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b5c266b9ac83dedf0e0385ad78514949e6d89491269e7065bee51d2bb8ec7373"
 dependencies = [
- "ahash 0.8.7",
+ "ahash 0.8.9",
  "gethostname",
  "log",
  "serde",
@@ -4320,7 +4305,7 @@ dependencies = [
  "once_cell",
  "proc-macro2",
  "quote",
- "syn 2.0.48",
+ "syn 2.0.50",
  "wasm-bindgen-shared",
 ]
 
@@ -4354,7 +4339,7 @@ checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.48",
+ "syn 2.0.50",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
@@ -4381,7 +4366,7 @@ version = "0.22.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53"
 dependencies = [
- "ring 0.17.7",
+ "ring 0.17.8",
  "untrusted 0.9.0",
 ]
 
@@ -4640,7 +4625,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.48",
+ "syn 2.0.50",
 ]
 
 [[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 20a127c1..56ef8125 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -33,7 +33,6 @@ thiserror = "1.0"
 serde_valid = "0.16.3"
 serde_json = { version = "1.0.105", features = [] }
 serde_derive = "1.0.188"
-actix-web-httpauth = "0.8.1"
 actix-cors = "0.6.4"
 tracing-actix-web = "0.7.7"
 regex = "1.10.2"
diff --git a/src/middleware/authentication/manager_middleware.rs b/src/middleware/authentication/manager_middleware.rs
index 0a72c8d3..7b9dc6b9 100644
--- a/src/middleware/authentication/manager_middleware.rs
+++ b/src/middleware/authentication/manager_middleware.rs
@@ -29,7 +29,7 @@ where
     fn call(&self, mut req: ServiceRequest) -> Self::Future {
         let service = self.service.clone();
         async move {
-            method::try_oauth(&mut req).await?
+            let _ = method::try_oauth(&mut req).await?
             || method::try_hmac(&mut req).await?
             || method::anonym(&mut req)?;
 
diff --git a/src/middleware/authentication/method/f_oauth.rs b/src/middleware/authentication/method/f_oauth.rs
index 5566f49b..5befe353 100644
--- a/src/middleware/authentication/method/f_oauth.rs
+++ b/src/middleware/authentication/method/f_oauth.rs
@@ -9,6 +9,7 @@ use std::sync::Arc;
 #[tracing::instrument(name = "try authenticate via bearer")]
 pub async fn try_oauth(req: &mut ServiceRequest) -> Result<bool, String> {
     let authentication = get_header::<String>(&req, "authorization")?; //todo
+    println!("{authentication:?}");
     if authentication.is_none() {
         return Ok(false);
     }
diff --git a/src/startup.rs b/src/startup.rs
index 279044e6..5e57dc85 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -8,7 +8,6 @@ use actix_web::{
     App, HttpServer,
 };
 use crate::middleware;
-use actix_web_httpauth::middleware::HttpAuthentication;
 use sqlx::{Pool, Postgres};
 use std::net::TcpListener;
 use tracing_actix_web::TracingLogger;

From 0e5901da64a4987dee929a45d5d93a874b6dcb3c Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Tue, 20 Feb 2024 18:30:50 +0200
Subject: [PATCH 178/284] 30-access-policies Bearer token extract logic

---
 .../authentication/method/f_oauth.rs          | 21 +++++++++++++++----
 1 file changed, 17 insertions(+), 4 deletions(-)

diff --git a/src/middleware/authentication/method/f_oauth.rs b/src/middleware/authentication/method/f_oauth.rs
index 5befe353..2b8ccb81 100644
--- a/src/middleware/authentication/method/f_oauth.rs
+++ b/src/middleware/authentication/method/f_oauth.rs
@@ -6,17 +6,30 @@ use crate::forms;
 use reqwest::header::{ACCEPT, CONTENT_TYPE};
 use std::sync::Arc;
 
+fn try_extract_token(authentication: String) -> Result<String, String> {
+    let mut authentication_parts = authentication.splitn(2, ' ');
+    match authentication_parts.next() {
+        Some("Bearer") => {}
+        _ => return Err("Bearer missing scheme".to_string())
+    }
+    let token = authentication_parts.next();
+    if token.is_none() {
+        return Err("Empty bearer token".to_string());
+    }
+
+    Ok(token.unwrap().into())
+}
+
 #[tracing::instrument(name = "try authenticate via bearer")]
 pub async fn try_oauth(req: &mut ServiceRequest) -> Result<bool, String> {
-    let authentication = get_header::<String>(&req, "authorization")?; //todo
-    println!("{authentication:?}");
+    let authentication = get_header::<String>(&req, "authorization")?;
     if authentication.is_none() {
         return Ok(false);
     }
 
+    let token = try_extract_token(authentication.unwrap())?; 
     let settings = req.app_data::<web::Data<Settings>>().unwrap();
-    let token = "abc"; //todo
-    let user = match fetch_user(settings.auth_url.as_str(), token).await {
+    let user = match fetch_user(settings.auth_url.as_str(), &token).await {
         Ok(user) => user,
         Err(err) => {
             return Err(format!("{}", err));

From 35815f61cccae763bfce7221661fabde219b98a1 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Wed, 21 Feb 2024 16:36:36 +0200
Subject: [PATCH 179/284] 30-access-policies user_fetch improved

---
 .../authentication/method/f_oauth.rs           | 18 ++++++++----------
 src/routes/client/disable.rs                   |  2 ++
 src/routes/client/enable.rs                    |  2 ++
 src/routes/client/update.rs                    |  2 ++
 4 files changed, 14 insertions(+), 10 deletions(-)

diff --git a/src/middleware/authentication/method/f_oauth.rs b/src/middleware/authentication/method/f_oauth.rs
index 2b8ccb81..efe215c6 100644
--- a/src/middleware/authentication/method/f_oauth.rs
+++ b/src/middleware/authentication/method/f_oauth.rs
@@ -29,21 +29,19 @@ pub async fn try_oauth(req: &mut ServiceRequest) -> Result<bool, String> {
 
     let token = try_extract_token(authentication.unwrap())?; 
     let settings = req.app_data::<web::Data<Settings>>().unwrap();
-    let user = match fetch_user(settings.auth_url.as_str(), &token).await {
-        Ok(user) => user,
-        Err(err) => {
-            return Err(format!("{}", err));
-        }
-    }; //todo . process the err
+    let user = fetch_user(settings.auth_url.as_str(), &token)
+        .await
+        .map_err(|err| format!("{err}"))?;
+
+    let accesscontrol_vals = actix_casbin_auth::CasbinVals {
+        subject: user.id.clone(),
+        domain: None,
+    };
 
     if req.extensions_mut().insert(Arc::new(user)).is_some() {
         return Err("user already logged".to_string());
     }
 
-    let accesscontrol_vals = actix_casbin_auth::CasbinVals {
-        subject: String::from("alice"), //todo username or anonymous
-        domain: None,
-    };
     if req.extensions_mut().insert(accesscontrol_vals).is_some() {
         return Err("sth wrong with access control".to_string());
     }
diff --git a/src/routes/client/disable.rs b/src/routes/client/disable.rs
index 70d2a1cb..3c46fa65 100644
--- a/src/routes/client/disable.rs
+++ b/src/routes/client/disable.rs
@@ -15,6 +15,8 @@ pub async fn disable_handler(
     pg_pool: web::Data<PgPool>,
     path: web::Path<(i32,)>,
 ) -> Result<impl Responder> {
+    //todo. the owner
+    //todo. add admin endpoint
     let client_id = path.0;
     let mut client = db::client::fetch(pg_pool.get_ref(), client_id)
         .await
diff --git a/src/routes/client/enable.rs b/src/routes/client/enable.rs
index c87fc445..ca36451c 100644
--- a/src/routes/client/enable.rs
+++ b/src/routes/client/enable.rs
@@ -16,6 +16,8 @@ pub async fn enable_handler(
     pg_pool: web::Data<PgPool>,
     path: web::Path<(i32,)>,
 ) -> Result<impl Responder> {
+    //todo the owner
+    //todo add admin endpoint
     let client_id = path.0;
     let mut client = db::client::fetch(pg_pool.get_ref(), client_id)
         .await
diff --git a/src/routes/client/update.rs b/src/routes/client/update.rs
index 5f19de51..fe8db12e 100644
--- a/src/routes/client/update.rs
+++ b/src/routes/client/update.rs
@@ -15,6 +15,8 @@ pub async fn update_handler(
     pg_pool: web::Data<PgPool>,
     path: web::Path<(i32,)>,
 ) -> Result<impl Responder> {
+    //todo owner
+    //todo admin endpoint
     let client_id = path.0;
     let mut client = db::client::fetch(pg_pool.get_ref(), client_id)
         .await

From e7661b2608f6afc3e19b9c36b30964ea0910b38f Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Sat, 24 Feb 2024 21:22:45 +0200
Subject: [PATCH 180/284] refactoring 70%

---
 src/forms/stack/app.rs              | 37 +++++++++++------------------
 src/forms/stack/compose_networks.rs |  2 ++
 src/forms/stack/custom.rs           |  6 ++---
 src/forms/stack/docker_image.rs     | 21 +++++++++++++---
 src/forms/stack/network.rs          | 31 ------------------------
 src/forms/stack/service_networks.rs |  8 +++----
 src/forms/stack/volume.rs           |  5 ++--
 src/forms/stack1.rs                 | 14 -----------
 src/helpers/stack/builder.rs        |  4 ++--
 src/main.rs                         |  1 -
 src/routes/stack/update.rs          |  2 +-
 tests/custom-stack-payload.json     |  3 +--
 12 files changed, 48 insertions(+), 86 deletions(-)

diff --git a/src/forms/stack/app.rs b/src/forms/stack/app.rs
index a46374e1..6b548a19 100644
--- a/src/forms/stack/app.rs
+++ b/src/forms/stack/app.rs
@@ -5,7 +5,7 @@ use serde_json::Value;
 use serde::{Deserialize, Serialize};
 use serde_valid::Validate;
 use crate::forms::stack::network::Network;
-use crate::forms::stack::replace_id_with_name;
+use crate::forms::stack::{DockerImage, replace_id_with_name};
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct App {
@@ -95,35 +95,21 @@ impl App {
         tracing::debug!("Named volumes: {:?}", named_volumes);
         named_volumes
     }
-}
-
-impl AsRef<forms::stack::DockerImage> for App {
-    fn as_ref(&self) -> &forms::stack::DockerImage {
-        &self.docker_image
-    }
-}
 
-impl TryFrom<&App> for dctypes::Service {
-    type Error = String;
 
-    fn try_from(args:(&App, Vec<Network>)) -> Result<Self, Self::Error> {
-        let (app, all_networks) = args;
+    pub(crate) fn try_into_service(&self, all_networks: &Vec<Network>) -> Result<dctypes::Service, String> {
 
         let mut service = dctypes::Service {
-            image: Some(app.docker_image.to_string()),
+            image: Some(self.docker_image.to_string()),
             ..Default::default()
         };
 
-        let networks = dctypes::Networks::try_from(&app.network).unwrap_or_default();
+        let networks = dctypes::Networks::try_from(&self.network).unwrap_or_default();
 
-        // @todo refactoring all_networks and
-        // let networks = replace_id_with_name(service_networks, all_networks);
-        // service.networks = Networks::Simple(networks);
-
-        let networks = replace_id_with_name(networks, &all_networks);
+        let networks = replace_id_with_name(networks, all_networks);
         service.networks = dctypes::Networks::Simple(networks);
 
-        let ports: Vec<dctypes::Port> = match &app.ports {
+        let ports: Vec<dctypes::Port> = match &self.ports {
             Some(ports) => {
                 let mut collector = vec![];
                 for port in ports {
@@ -134,7 +120,7 @@ impl TryFrom<&App> for dctypes::Service {
             None => vec![]
         };
 
-        let volumes: Vec<dctypes::Volumes> = match &app.volumes {
+        let volumes: Vec<dctypes::Volumes> = match &self.volumes {
             Some(volumes) => {
                 let mut collector = vec![];
                 for volume in volumes {
@@ -147,7 +133,7 @@ impl TryFrom<&App> for dctypes::Service {
         };
 
         let mut envs = IndexMap::new();
-        for item in app.environment.environment.clone().unwrap_or_default() {
+        for item in self.environment.environment.clone().unwrap_or_default() {
             let items = item
                 .into_iter()
                 .map(|(k, v)| (k, Some(dctypes::SingleValue::String(v.clone()))))
@@ -156,7 +142,6 @@ impl TryFrom<&App> for dctypes::Service {
             envs.extend(items);
         }
 
-        service.networks = networks;
         service.ports = dctypes::Ports::Long(ports);
         service.restart = Some("always".to_owned());
         service.volumes = volumes;
@@ -165,3 +150,9 @@ impl TryFrom<&App> for dctypes::Service {
         Ok(service)
     }
 }
+
+impl AsRef<forms::stack::DockerImage> for App {
+    fn as_ref(&self) -> &forms::stack::DockerImage {
+        &self.docker_image
+    }
+}
diff --git a/src/forms/stack/compose_networks.rs b/src/forms/stack/compose_networks.rs
index 0b229abd..afbd6f81 100644
--- a/src/forms/stack/compose_networks.rs
+++ b/src/forms/stack/compose_networks.rs
@@ -1,6 +1,7 @@
 use serde::{Deserialize, Serialize};
 use docker_compose_types as dctypes;
 use indexmap::IndexMap;
+use crate::forms::stack;
 use crate::forms::stack::network::Network;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
@@ -38,3 +39,4 @@ impl Into<IndexMap<String, dctypes::MapOrEmpty<dctypes::NetworkSettings>>> for C
         networks
     }
 }
+
diff --git a/src/forms/stack/custom.rs b/src/forms/stack/custom.rs
index 007e0af6..872e34e0 100644
--- a/src/forms/stack/custom.rs
+++ b/src/forms/stack/custom.rs
@@ -68,20 +68,20 @@ impl Custom {
         let all_networks = self.networks.networks.clone().unwrap_or(vec![]);
 
         for app_type in &self.web {
-            let service = dctypes::Service::try_from((&app_type.app, &all_networks))?;
+            let service = app_type.app.try_into_service(&all_networks)?;
             services.insert(app_type.app.code.clone().to_owned(), Some(service));
         }
 
         if let Some(srvs) = &self.service {
             for app_type in srvs {
-                let service = dctypes::Service::try_from((&app_type.app, &all_networks))?;
+                let service = app_type.app.try_into_service(&all_networks)?;
                 services.insert(app_type.app.code.clone().to_owned(), Some(service));
             }
         }
 
         if let Some(features) = &self.feature {
             for app_type in features {
-                let service = dctypes::Service::try_from((&app_type.app, &all_networks))?;
+                let service = app_type.app.try_into_service(&all_networks)?;
                 services.insert(app_type.app.code.clone().to_owned(), Some(service));
             }
         }
diff --git a/src/forms/stack/docker_image.rs b/src/forms/stack/docker_image.rs
index b36a0994..85a335a7 100644
--- a/src/forms/stack/docker_image.rs
+++ b/src/forms/stack/docker_image.rs
@@ -1,18 +1,25 @@
 use serde::{Deserialize, Serialize};
 use serde_valid::Validate;
 use std::fmt;
+use crate::helpers::dockerhub::DockerHub;
+
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct DockerImage {
-    #[validate(min_length = 3)]
+    // #[validate(min_length = 3)]
     #[validate(max_length = 50)]
+    // @todo conditional check, if not empty
+    // #[validate(pattern = r"^[a-z0-9]+([-_.][a-z0-9]+)*$")]
     pub dockerhub_user: Option<String>,
-    #[validate(min_length = 3)]
+    // #[validate(min_length = 3)]
     #[validate(max_length = 50)]
+    // @todo conditional check, if not empty
+    // #[validate(pattern = r"^[a-z0-9]+([-_.][a-z0-9]+)*$")]
     pub dockerhub_name: Option<String>,
-    #[validate(min_length = 3)]
+    // #[validate(min_length = 3)]
     #[validate(max_length = 100)]
     pub dockerhub_image: Option<String>,
+    pub dockerhub_password: Option<String>,
 }
 
 impl fmt::Display for DockerImage {
@@ -32,3 +39,11 @@ impl fmt::Display for DockerImage {
         )
     }
 }
+impl DockerImage {
+    #[tracing::instrument(name = "is_active")]
+    pub async fn is_active(&self) -> Result<bool, String> {
+        DockerHub::from(self).is_active().await
+    }
+}
+
+
diff --git a/src/forms/stack/network.rs b/src/forms/stack/network.rs
index 5f3b8ba2..a05fd540 100644
--- a/src/forms/stack/network.rs
+++ b/src/forms/stack/network.rs
@@ -60,34 +60,3 @@ impl Into<dctypes::NetworkSettings> for Network {
         }
     }
 }
-impl Into<IndexMap<String, dctypes::MapOrEmpty<dctypes::NetworkSettings>>> for stack::ComposeNetworks {
-    fn into(self) -> IndexMap<String, dctypes::MapOrEmpty<dctypes::NetworkSettings>> {
-
-        // let mut default_networks = vec![Network::default()];
-        let mut default_networks = vec![];
-
-        let networks = match self.networks {
-            None => {
-                default_networks
-            }
-            Some(mut nets) => {
-                if !nets.is_empty() {
-                    nets.append(&mut default_networks);
-                }
-                nets
-            }
-        };
-
-        let networks = networks
-            .into_iter()
-            .map(|net| {
-                (net.name.clone(), dctypes::MapOrEmpty::Map(net.into()))
-            }
-            )
-            .collect::<IndexMap<String, _>>();
-
-        tracing::debug!("networks collected {:?}", &networks);
-
-        networks
-    }
-}
diff --git a/src/forms/stack/service_networks.rs b/src/forms/stack/service_networks.rs
index f1b82676..97763e65 100644
--- a/src/forms/stack/service_networks.rs
+++ b/src/forms/stack/service_networks.rs
@@ -11,15 +11,15 @@ impl TryFrom<&ServiceNetworks> for dctypes::Networks {
 
     fn try_from(service_networks: &ServiceNetworks) -> Result<dctypes::Networks, Self::Error> {
         let mut default_networks = vec![];
-        let nets = match  service_networks.network.as_ref() {
+        let nets = match service_networks.network.as_ref() {
             Some(mut _nets) => {
                     _nets.append(&mut default_networks);
-                _nets
-            }
+                _nets.clone()
+           }
             None => {
                default_networks
             }
         };
-        Ok(dctypes::Networks::Simple(result))
+        Ok(dctypes::Networks::Simple(nets.into()))
     }
 }
diff --git a/src/forms/stack/volume.rs b/src/forms/stack/volume.rs
index faa7089a..ab4d3f84 100644
--- a/src/forms/stack/volume.rs
+++ b/src/forms/stack/volume.rs
@@ -50,11 +50,12 @@ impl TryInto<dctypes::AdvancedVolumes> for &Volume {
     }
 }
 
-impl Into<dctypes::ComposeVolume> for Volume {
+impl Into<dctypes::ComposeVolume> for &Volume {
     fn into(self) -> dctypes::ComposeVolume {
         // let's create a symlink to /var/docker/volumes in project docroot
         let mut driver_opts = IndexMap::default();
-        let host_path = self.host_path.unwrap();
+        let host_path = self.host_path.clone().unwrap_or_else(String::default);
+        // @todo check if host_path is required argument
         driver_opts.insert(String::from("type"), Some(dctypes::SingleValue::String("none".to_string())));
         driver_opts.insert(String::from("o"), Some(dctypes::SingleValue::String("bind".to_string())));
         // @todo move to config stack docroot on host
diff --git a/src/forms/stack1.rs b/src/forms/stack1.rs
index d63764b5..888b42fe 100644
--- a/src/forms/stack1.rs
+++ b/src/forms/stack1.rs
@@ -4,7 +4,6 @@ use serde_valid::Validate;
 use std::fmt;
 use indexmap::IndexMap;
 use regex::Regex;
-use crate::helpers::dockerhub::DockerHub;
 use crate::helpers::stack::dctypes::SingleValue;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
@@ -102,19 +101,6 @@ impl fmt::Display for DockerImage {
 }
 
 
-impl DockerImage {
-    #[tracing::instrument(name = "is_active")]
-    pub async fn is_active(&self) -> Result<bool, String> {
-        DockerHub::from(self).is_active().await
-    }
-}
-
-
-impl AsRef<DockerImage> for App {
-    fn as_ref(&self) -> &DockerImage {
-        &self.docker_image
-    }
-}
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct StackForm {
diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index 404f9e9c..1539a9a3 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -31,8 +31,8 @@ impl DcBuilder {
 
 // <<<<<<< HEAD
         let apps = forms::stack::Stack::try_from(&self.stack)?; 
-        let  services = apps.custom.services()?;
-        let  named_volumes = apps.custom.named_volumes()?;
+        let services = apps.custom.services()?;
+        let named_volumes = apps.custom.named_volumes()?;
 
         // let all_networks = &apps.custom.networks.networks.clone().unwrap_or(vec![]);
         let networks = apps.custom.networks.clone();
diff --git a/src/main.rs b/src/main.rs
index 86d9ba0c..8132f582 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -4,7 +4,6 @@ use stacker::startup::run;
 use stacker::telemetry::{get_subscriber, init_subscriber};
 use std::net::TcpListener;
 
-
 #[actix_web::main]
 async fn main() -> std::io::Result<()> {
     let subscriber = get_subscriber("stacker".into(), "info".into());
diff --git a/src/routes/stack/update.rs b/src/routes/stack/update.rs
index 23123ae9..ba4d7284 100644
--- a/src/routes/stack/update.rs
+++ b/src/routes/stack/update.rs
@@ -41,7 +41,7 @@ pub async fn update(
         return Err(JsonResponse::<models::Stack>::build().bad_request("Can not access docker image"));
     }
 
-    let body: Value = serde_json::to_value::<forms::stack::Stack>(form.into_inner())
+    let body: Value = serde_json::to_value::<forms::stack::Stack>(form_inner)
         .map_err(|err| 
             JsonResponse::<models::Stack>::build().bad_request(format!("{err}"))
         )?; 
diff --git a/tests/custom-stack-payload.json b/tests/custom-stack-payload.json
index 2ec622ba..ab48715a 100644
--- a/tests/custom-stack-payload.json
+++ b/tests/custom-stack-payload.json
@@ -1,2 +1 @@
-{"commonDomain":"","domainList":{},"region":"fsn1","zone":null,"server":"cx11","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":false,"disk_type":"pd-standart","cloud_token":"****************************","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"networks":[{
-  "name": "default_network"}, {"name":"mynetwork"}],"web":[{"name":"MyApp","code":"myapp","domain":"myapp.com","shared_ports":[{"host_port":"","container_port":"8080"}],"versions":[],"custom":true,"type":"web","network":["default_network", "mynetwork"],"restart":"no","timestamp":"2024-01-15T09:47:46.580Z","_id":"lreqrvv81ifrmng39","dockerhub_user":"","dockerhub_name":"nginx", "dockerhub_password":"", "dockerhub_image": "None", "environment":[{"key":"SOMEENVVAR1","value":"SOMEENVVAR_VALUE1"},{"key":"SOMEENVVAR2","value":"SOMEENVVAR_VALUE2"}],"volumes":[]}],"feature":[],"service":[],"servers_count":3,"project_git_url":"info@optimum-web.com","project_name":"Sample2024","custom_stack_code":"sample2024"}}
\ No newline at end of file
+{"commonDomain":"","domainList":{},"region":"","zone":null,"server":"cx11","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":true,"disk_type":"pd-standart","cloud_token":"*******************","cloud_key":"*********","cloud_secret":"****************","disk_size":40,"provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-annually","custom":{"networks":[{"name":"default_network","id":"lsnd2ttg3ivhw3db5"}],"web":[{"_etag":null,"_id":"lsq2tdff3c0acf2lj","_created":"2023-04-28T09:46:19.470502","_updated":"2024-02-09T13:44:36.854036","name":"PostgREST","code":"postgrest","role":["postgrest"],"type":"web","default":false,"popularity":null,"descr":null,"ports":{"public":["3000","8080"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":null,"category_id":null,"parent_app_id":null,"full_description":null,"description":null,"plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.0","ram_size":null,"disk_size":null,"dockerhub_image":"postgrest","form":null,"requirements":null,"docker_image_is_internal":true,"custom_preset":{"volumes":[],"environment":[{"key":"PGRST_OPENAPI_SERVER_PROXY_URI","value":"http://DOMAIN_NAME:3000"},{"key":"PGRST_DB_ANON_ROLE","value":"anon"},{"key":"PGRST_DB_SCHEMA","value":"public"},{"key":"PGHOST","value":"postgresql_host"},{"key":"PGPORT","value":"5432"},{"key":"PGUSER","value":"user"},{"key":"PGDATABASE","value":"database"},{"key":"PGPASSWORD ","value":"password"}],"shared_ports":[{"host_port":"3000","container_port":"3000"}],"restart":"always"},"category":[null],"group":[],"versions":[{"_id":556,"name":"Postgrest","version":"latest","update_status":"published","tag":"latest"}],"links":[{"url":"https://postgrest.org","title":"Vendor","type":"vendor","follow":false},{"repo_owner":"PostgREST","repo_name":"postgrest","type":"github","follow":false}],"version":{"_id":556,"name":"Postgrest","version":"latest","update_status":"published","tag":"latest"},"network":["lsnd2ttg3ivhw3db5"],"restart":"always","timestamp":"2024-02-17T12:50:01.659Z","volumes":[],"environment":[{"key":"PGRST_OPENAPI_SERVER_PROXY_URI","value":"http://DOMAIN_NAME:3000"},{"key":"PGRST_DB_ANON_ROLE","value":"anon"},{"key":"PGRST_DB_SCHEMA","value":"public"},{"key":"PGHOST","value":"postgresql_host"},{"key":"PGPORT","value":"5432"},{"key":"PGUSER","value":"user"},{"key":"PGDATABASE","value":"database"},{"key":"PGPASSWORD ","value":"password"}],"shared_ports":[{"host_port":"3000","container_port":"3000"}],"domain":"","dockerhub_name":"postgrest"}],"feature":[],"service":[],"servers_count":3,"project_name":"Databases","custom_stack_code":"databases"}}
\ No newline at end of file

From f2fa00efbea302ed43d31926840da032b671306c Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Sat, 24 Feb 2024 23:07:55 +0200
Subject: [PATCH 181/284] refactoring ~75%, code compiles

---
 src/forms/stack/service_networks.rs |  6 ++--
 src/helpers/stack/builder.rs        | 50 +----------------------------
 2 files changed, 3 insertions(+), 53 deletions(-)

diff --git a/src/forms/stack/service_networks.rs b/src/forms/stack/service_networks.rs
index 97763e65..03f5f45d 100644
--- a/src/forms/stack/service_networks.rs
+++ b/src/forms/stack/service_networks.rs
@@ -10,14 +10,12 @@ impl TryFrom<&ServiceNetworks> for dctypes::Networks {
     type Error = ();
 
     fn try_from(service_networks: &ServiceNetworks) -> Result<dctypes::Networks, Self::Error> {
-        let mut default_networks = vec![];
         let nets = match service_networks.network.as_ref() {
-            Some(mut _nets) => {
-                    _nets.append(&mut default_networks);
+            Some(_nets) => {
                 _nets.clone()
            }
             None => {
-               default_networks
+               vec![]
             }
         };
         Ok(dctypes::Networks::Simple(nets.into()))
diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs
index 1539a9a3..e17d3642 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/stack/builder.rs
@@ -29,8 +29,7 @@ impl DcBuilder {
             ..Default::default()
         };
 
-// <<<<<<< HEAD
-        let apps = forms::stack::Stack::try_from(&self.stack)?; 
+        let apps = forms::stack::Stack::try_from(&self.stack)?;
         let services = apps.custom.services()?;
         let named_volumes = apps.custom.named_volumes()?;
 
@@ -40,53 +39,6 @@ impl DcBuilder {
 
         if !named_volumes.is_empty() {
             compose_content.volumes = dctypes::TopLevelVolumes(named_volumes);
-// =======
-//         match _stack {
-//             Ok(apps) => {
-//
-//                 for app_type in &apps.custom.web {
-//                     let mut service = app_type.app.try_into_service();
-//                     let service_networks = service.networks.clone();
-//                     let networks = replace_id_with_name(service_networks, all_networks);
-//                     service.networks = Networks::Simple(networks);
-//                     services.insert(app_type.app.code.clone().to_owned(), Some(service));
-//                     named_volumes.extend(extract_named_volumes(app_type.app.clone()));
-//                 }
-//
-//                 if let Some(srvs) = apps.custom.service {
-//                     for app_type in srvs {
-//                         let mut service = app_type.app.try_into_service();
-//                         let service_networks = service.networks.clone();
-//                         let networks = replace_id_with_name(service_networks, all_networks);
-//                         service.networks = Networks::Simple(networks);
-//                         services.insert(app_type.app.code.clone().to_owned(), Some(service));
-//                         named_volumes.extend(extract_named_volumes(app_type.app.clone()));
-//                     }
-//                 }
-//
-//                 if let Some(features) = apps.custom.feature {
-//                     for app_type in features {
-//                         let mut service = app_type.app.try_into_service();
-//                         let service_networks = service.networks.clone();
-//                         let networks = replace_id_with_name(service_networks, all_networks);
-//                         service.networks = Networks::Simple(networks);
-//                         services.insert(app_type.app.code.clone().to_owned(), Some(service));
-//                         named_volumes.extend(extract_named_volumes(app_type.app.clone()));
-//                     }
-//                 }
-//
-//                 let networks = apps.custom.networks.clone();
-//                 compose_content.networks = ComposeNetworks(networks.into());
-//
-//                 if !named_volumes.is_empty() {
-//                     compose_content.volumes = TopLevelVolumes(named_volumes);
-//                 }
-//
-//             }
-//             Err(e) => {
-//                 tracing::debug!("Unpack stack form error {:?}", e);
-//             }
-// >>>>>>> issue-16
         }
 
         tracing::debug!("services {:?}", &services);

From 1cc25627aba9c6131e52a4ae100224aa48610b3c Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Sun, 25 Feb 2024 08:52:44 +0200
Subject: [PATCH 182/284] delete extra file

---
 src/forms/stack/service_networks.rs |  35 +++
 src/forms/stack1.rs                 | 423 ----------------------------
 2 files changed, 35 insertions(+), 423 deletions(-)
 delete mode 100644 src/forms/stack1.rs

diff --git a/src/forms/stack/service_networks.rs b/src/forms/stack/service_networks.rs
index 03f5f45d..17b51c53 100644
--- a/src/forms/stack/service_networks.rs
+++ b/src/forms/stack/service_networks.rs
@@ -21,3 +21,38 @@ impl TryFrom<&ServiceNetworks> for dctypes::Networks {
         Ok(dctypes::Networks::Simple(nets.into()))
     }
 }
+
+// IndexMap
+//
+// impl Into<IndexMap<String, MapOrEmpty<NetworkSettings>>> for stack::ComposeNetworks {
+//     fn into(self) -> IndexMap<String, MapOrEmpty<NetworkSettings>> {
+//
+//         // let mut default_networks = vec![Network::default()];
+//         let mut default_networks = vec![];
+//
+//         let networks = match self.networks {
+//             None => {
+//                 default_networks
+//             }
+//             Some(mut nets) => {
+//                 if !nets.is_empty() {
+//                     nets.append(&mut default_networks);
+//                 }
+//                 nets
+//             }
+//         };
+//
+//         let networks = networks
+//             .into_iter()
+//             .map(|net| {
+//                 (net.name.clone(), MapOrEmpty::Map(net.into()))
+//             }
+//             )
+//             .collect::<IndexMap<String, _>>();
+//
+//         tracing::debug!("networks collected {:?}", &networks);
+//
+//         networks
+//     }
+// }
+
diff --git a/src/forms/stack1.rs b/src/forms/stack1.rs
deleted file mode 100644
index 888b42fe..00000000
--- a/src/forms/stack1.rs
+++ /dev/null
@@ -1,423 +0,0 @@
-use serde::{Deserialize, Serialize};
-use serde_json::Value;
-use serde_valid::Validate;
-use std::fmt;
-use indexmap::IndexMap;
-use regex::Regex;
-use crate::helpers::stack::dctypes::SingleValue;
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct Role {
-    pub role: Option<Vec<String>>,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct Requirements {
-    #[validate(min_length = 1)]
-    #[validate(max_length = 10)]
-    #[validate(pattern = r"^\d+\.?[0-9]+$")]
-    pub cpu: Option<String>,
-    #[validate(min_length = 1)]
-    #[validate(max_length = 10)]
-    #[validate(pattern = r"^\d+G$")]
-    #[serde(rename = "disk_size")]
-    pub disk_size: Option<String>,
-    #[serde(rename = "ram_size")]
-    #[validate(min_length = 1)]
-    #[validate(max_length = 10)]
-    #[validate(pattern = r"^\d+G$")]
-    pub ram_size: Option<String>,
-}
-
-fn validate_non_empty(v: &Option<String>) -> Result<(), serde_valid::validation::Error> {
-    if v.is_none() {
-        return Ok(());
-    }
-
-    if let Some(value) = v {
-        if value.is_empty() {
-            return Ok(());
-        }
-
-        // #[validate(pattern = r"^\d{2,6}$")]
-        let re = Regex::new(r"^\d{2,6}$").unwrap();
-
-        if !re.is_match(value.as_str()) {
-            return Err(serde_valid::validation::Error::Custom("Port is not valid.".to_owned()));
-        }
-    }
-
-    Ok(())
-}
-
-#[derive(Validate)]
-struct Data {
-    val: i32,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct Port {
-    #[validate(custom(|v| validate_non_empty(v)))]
-    pub host_port: Option<String>,
-    #[validate(pattern = r"^\d{2,6}$")]
-    pub container_port: String,
-    #[validate(enumerate("tcp", "udp"))]
-    pub protocol: Option<String>,
-}
-
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct DockerImage {
-    // #[validate(min_length = 3)]
-    #[validate(max_length = 50)]
-    // @todo conditional check, if not empty
-    // #[validate(pattern = r"^[a-z0-9]+([-_.][a-z0-9]+)*$")]
-    pub dockerhub_user: Option<String>,
-    // #[validate(min_length = 3)]
-    #[validate(max_length = 50)]
-    // @todo conditional check, if not empty
-    // #[validate(pattern = r"^[a-z0-9]+([-_.][a-z0-9]+)*$")]
-    pub dockerhub_name: Option<String>,
-    // #[validate(min_length = 3)]
-    #[validate(max_length = 100)]
-    pub dockerhub_image: Option<String>,
-    pub dockerhub_password: Option<String>,
-}
-
-impl fmt::Display for DockerImage {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let dh_image = self.dockerhub_image.as_ref().map(String::as_str).unwrap_or("");
-        let dh_nmspc = self.dockerhub_user.as_ref().map(String::as_str).unwrap_or("");
-        let dh_name = self.dockerhub_name.as_ref().map(String::as_str).unwrap_or("");
-
-        write!(
-            f,
-            "{}{}{}",
-            if !dh_nmspc.is_empty() { format!("{}/", dh_nmspc) } else { String::new() },
-            if !dh_name.is_empty() { dh_name } else { dh_image },
-            if !dh_name.contains(":") && dh_image.is_empty() { ":latest".to_string() } else { String::new() },
-        )
-    }
-}
-
-
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct StackForm {
-    #[validate(max_length = 255)]
-    #[serde(rename = "commonDomain")]
-    pub common_domain: Option<String>,
-    pub domain_list: Option<DomainList>,
-    #[validate(min_length = 2)]
-    #[validate(max_length = 255)]
-    pub stack_code: Option<String>,
-    #[validate(min_length = 2)]
-    #[validate(max_length = 50)]
-    pub region: String,
-    #[validate(min_length = 2)]
-    #[validate(max_length = 50)]
-    pub zone: Option<String>,
-    #[validate(min_length = 2)]
-    #[validate(max_length = 50)]
-    pub server: String,
-    #[validate(min_length = 2)]
-    #[validate(max_length = 50)]
-    pub os: String,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 50)]
-    pub ssl: String,
-    pub vars: Option<Vec<Var>>,
-    pub integrated_features: Option<Vec<Value>>,
-    pub extended_features: Option<Vec<Value>>,
-    pub subscriptions: Option<Vec<String>>,
-    pub form_app: Option<Vec<String>>,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 50)]
-    pub disk_type: Option<String>,
-    pub save_token: bool,
-    #[validate(min_length = 10)]
-    #[validate(max_length = 255)]
-    pub cloud_token: String,
-    #[validate(min_length = 2)]
-    #[validate(max_length = 50)]
-    pub provider: String,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 50)]
-    pub selected_plan: String,
-    #[validate]
-    pub custom: Custom,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-#[serde(rename_all = "snake_case")]
-pub struct StackPayload {
-    pub(crate) id: Option<i32>,
-    pub(crate) user_token: Option<String>,
-    pub(crate) user_email: Option<String>,
-    #[serde(rename = "commonDomain")]
-    pub common_domain: String,
-    pub domain_list: Option<DomainList>,
-    pub region: String,
-    pub zone: Option<String>,
-    pub server: String,
-    pub os: String,
-    pub ssl: String,
-    pub vars: Option<Vec<Var>>,
-    #[serde(rename = "integrated_features")]
-    pub integrated_features: Option<Vec<Value>>,
-    #[serde(rename = "extended_features")]
-    pub extended_features: Option<Vec<Value>>,
-    pub subscriptions: Option<Vec<String>>,
-    pub form_app: Option<Vec<String>>,
-    pub disk_type: Option<String>,
-    #[serde(rename = "save_token")]
-    pub save_token: bool,
-    #[serde(rename = "cloud_token")]
-    pub cloud_token: String,
-    pub provider: String,
-    pub stack_code: String,
-    #[serde(rename = "selected_plan")]
-    pub selected_plan: String,
-    pub custom: Custom,
-    pub docker_compose: Option<Vec<u8>>,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct DomainList {}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct Var {}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct Price {
-    pub value: f64,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct Custom {
-    #[validate]
-    pub web: Vec<Web>,
-    #[validate]
-    pub feature: Option<Vec<Feature>>,
-    #[validate]
-    pub service: Option<Vec<Service>>,
-    #[validate(minimum = 0)]
-    #[validate(maximum = 10)]
-    pub servers_count: u32,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 50)]
-    pub custom_stack_code: String,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 255)]
-    pub project_git_url: Option<String>,
-    pub custom_stack_category: Option<Vec<String>>,
-    pub custom_stack_short_description: Option<String>,
-    pub custom_stack_description: Option<String>,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 255)]
-    pub project_name: String,
-    pub project_overview: Option<String>,
-    pub project_description: Option<String>,
-    #[serde(flatten)]
-    pub networks: ComposeNetworks, // all networks
-}
-
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct App {
-    #[serde(rename = "_etag")]
-    #[validate(min_length = 3)]
-    #[validate(max_length = 255)]
-    pub etag: Option<String>,
-    #[serde(rename = "_id")]
-    pub id: String,
-    #[serde(rename = "_created")]
-    pub created: Option<String>,
-    #[serde(rename = "_updated")]
-    pub updated: Option<String>,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 50)]
-    pub name: String,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 50)]
-    pub code: String,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 50)]
-    #[serde(rename = "type")]
-    pub type_field: String,
-    #[serde(flatten)]
-    pub role: Role,
-    pub default: Option<bool>,
-    pub versions: Option<Vec<Version>>,
-    #[serde(flatten)]
-    #[validate]
-    pub docker_image: DockerImage,
-    #[serde(flatten)]
-    #[validate]
-    pub requirements: Requirements,
-    #[validate(minimum = 1)]
-    pub popularity: Option<u32>,
-    pub commercial: Option<bool>,
-    pub subscription: Option<Value>,
-    pub autodeploy: Option<bool>,
-    pub suggested: Option<bool>,
-    pub dependency: Option<Value>,
-    pub avoid_render: Option<bool>,
-    pub price: Option<Price>,
-    pub icon: Option<Icon>,
-    pub domain: Option<String>,
-    pub category_id: Option<u32>,
-    pub parent_app_id: Option<u32>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub descr: Option<String>,
-    pub full_description: Option<String>,
-    pub description: Option<String>,
-    pub plan_type: Option<String>,
-    pub ansible_var: Option<String>,
-    pub repo_dir: Option<String>,
-    pub url_app: Option<String>,
-    pub url_git: Option<String>,
-    #[validate(enumerate("always", "no", "unless-stopped", "on-failure"))]
-    pub restart: String,
-    pub volumes: Option<Vec<Volume>>,
-    #[serde(flatten)]
-    pub environment: Environment,
-    #[serde(flatten)]
-    pub network: ServiceNetworks,
-    #[validate]
-    pub shared_ports: Option<Vec<Port>>,
-}
-
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct EnvVar {
-    pub(crate) key: String,
-    pub(crate) value: String,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct Environment {
-    pub(crate) environment: Option<Vec<EnvVar>>,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct Volume {
-    pub host_path: Option<String>,
-    pub container_path: Option<String>,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct Volumes {
-    volumes: Vec<Volume>,
-}
-
-// pub(crate) type Networks = Option<Vec<String>>;
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct ServiceNetworks {
-    pub network: Option<Vec<String>>,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct NetworkDriver {
-    // not implemented
-}
-
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct Network {
-    pub(crate) id: String,
-    pub(crate) attachable: Option<bool>,
-    pub(crate) driver: Option<String>,
-    pub(crate) driver_opts: Option<NetworkDriver>,
-    pub(crate) enable_ipv6: Option<bool>,
-    pub(crate) internal: Option<bool>,
-    pub(crate) external: Option<bool>,
-    pub(crate) ipam: Option<String>,
-    pub(crate) labels: Option<String>,
-    pub(crate) name: String,
-}
-
-impl Default for Network {
-    fn default() -> Self {
-        // The case when we need at least one external network to be preconfigured
-        Network {
-            id: "default_network".to_string(),
-            attachable: None,
-            driver: None,
-            driver_opts: Default::default(),
-            enable_ipv6: None,
-            internal: None,
-            external: Some(true),
-            ipam: None,
-            labels: None,
-            name: "default_network".to_string(),
-        }
-    }
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct ComposeNetworks {
-    pub networks: Option<Vec<Network>>,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct Web {
-    #[serde(flatten)]
-    #[validate]
-    pub app: App,
-    pub custom: Option<bool>,
-    pub main: Option<bool>,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct Feature {
-    #[serde(flatten)]
-    #[validate]
-    pub app: App,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct Service {
-    #[serde(flatten)]
-    #[validate]
-    pub(crate) app: App,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct Icon {
-    pub light: IconLight,
-    pub dark: IconDark,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct IconLight {
-    pub width: i64,
-    pub height: i64,
-    pub image: String,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct IconDark {}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct Version {
-    #[serde(rename = "_etag")]
-    pub etag: Option<String>,
-    #[serde(rename = "_id")]
-    pub id: u32,
-    #[serde(rename = "_created")]
-    pub created: Option<String>,
-    #[serde(rename = "_updated")]
-    pub updated: Option<String>,
-    pub app_id: Option<u32>,
-    pub name: String,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 20)]
-    pub version: String,
-    #[serde(rename = "update_status")]
-    pub update_status: Option<String>,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 20)]
-    pub tag: String,
-}

From 6cd46bcfa8da62f8ce397d6c99d929336e013727 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Sun, 25 Feb 2024 09:33:44 +0200
Subject: [PATCH 183/284] new fixes from issue-16

---
 src/forms/stack/app.rs          |  19 +-
 src/forms/stack/custom.rs       |   3 +
 src/forms/stack/environment.rs  |   9 +-
 src/forms/stack/payload.rs      |   9 +-
 src/forms/stack/port.rs         |  10 +-
 src/forms/stack/requirements.rs |   6 +-
 src/forms/stack1.rs             | 462 ++++++++++++++++++++++++++++++++
 src/helpers/json.rs             |   4 +
 src/routes/stack/add.rs         |  13 +-
 src/routes/stack/deploy.rs      |   6 +-
 src/routes/stack/update.rs      |   4 +-
 src/routes/test/deploy.rs       |   7 +
 12 files changed, 522 insertions(+), 30 deletions(-)
 create mode 100644 src/forms/stack1.rs

diff --git a/src/forms/stack/app.rs b/src/forms/stack/app.rs
index 6b548a19..1eedd6b2 100644
--- a/src/forms/stack/app.rs
+++ b/src/forms/stack/app.rs
@@ -14,7 +14,7 @@ pub struct App {
     #[validate(max_length = 255)]
     pub etag: Option<String>,
     #[serde(rename = "_id")]
-    pub id: u32,
+    pub id: String,
     #[serde(rename = "_created")]
     pub created: Option<String>,
     #[serde(rename = "_updated")]
@@ -34,8 +34,10 @@ pub struct App {
     pub default: Option<bool>,
     pub versions: Option<Vec<forms::stack::Version>>,
     #[serde(flatten)]
-    pub docker_image: forms::stack::DockerImage,
+    #[validate]
+    pub docker_image: DockerImage,
     #[serde(flatten)]
+    #[validate]
     pub requirements: forms::stack::Requirements,
     #[validate(minimum = 1)]
     pub popularity: Option<u32>,
@@ -59,18 +61,15 @@ pub struct App {
     pub repo_dir: Option<String>,
     pub url_app: Option<String>,
     pub url_git: Option<String>,
-    pub restart: Option<String>,
+    #[validate(enumerate("always", "no", "unless-stopped", "on-failure"))]
+    pub restart: String,
     pub volumes: Option<Vec<forms::stack::Volume>>,
     #[serde(flatten)]
     pub environment: forms::stack::Environment,
     #[serde(flatten)]
     pub network: forms::stack::ServiceNetworks,
-    // #[serde(flatten)]
-    // pub ports: Ports,
-    #[serde(rename(deserialize = "sharedPorts"))]
-    #[serde(rename(serialize = "shared_ports"))]
-    #[serde(alias = "shared_ports")]
-    pub ports: Option<Vec<forms::stack::Port>>,
+    #[validate]
+    pub shared_ports: Option<Vec<forms::stack::Port>>,
 }
 
 impl App {
@@ -109,7 +108,7 @@ impl App {
         let networks = replace_id_with_name(networks, all_networks);
         service.networks = dctypes::Networks::Simple(networks);
 
-        let ports: Vec<dctypes::Port> = match &self.ports {
+        let ports: Vec<dctypes::Port> = match &self.shared_ports {
             Some(ports) => {
                 let mut collector = vec![];
                 for port in ports {
diff --git a/src/forms/stack/custom.rs b/src/forms/stack/custom.rs
index 872e34e0..b127a126 100644
--- a/src/forms/stack/custom.rs
+++ b/src/forms/stack/custom.rs
@@ -8,8 +8,11 @@ use crate::forms::stack::Network;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Custom {
+    #[validate]
     pub web: Vec<forms::stack::Web>,
+    #[validate]
     pub feature: Option<Vec<forms::stack::Feature>>,
+    #[validate]
     pub service: Option<Vec<forms::stack::Service>>,
     #[validate(minimum = 0)]
     #[validate(maximum = 10)]
diff --git a/src/forms/stack/environment.rs b/src/forms/stack/environment.rs
index 9b6e88d6..071d1592 100644
--- a/src/forms/stack/environment.rs
+++ b/src/forms/stack/environment.rs
@@ -1,7 +1,12 @@
 use serde::{Deserialize, Serialize};
-use std::collections::HashMap;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct Environment {
-    pub(crate) environment: Option<Vec<HashMap<String, String>>>,
+    pub(crate) environment: Option<Vec<EnvVar>>,
 }
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct EnvVar {
+    pub(crate) key: String,
+    pub(crate) value: String,
+}
+
diff --git a/src/forms/stack/payload.rs b/src/forms/stack/payload.rs
index 70be8410..ddcd9276 100644
--- a/src/forms/stack/payload.rs
+++ b/src/forms/stack/payload.rs
@@ -27,15 +27,16 @@ pub struct Payload {
     pub subscriptions: Option<Vec<String>>,
     pub form_app: Option<Vec<String>>,
     pub disk_type: Option<String>,
-    #[serde(rename = "save_token")]
     pub save_token: bool,
-    #[serde(rename = "cloud_token")]
-    pub cloud_token: String,
+    pub cloud_token: Option<String>,
+    pub cloud_key: Option<String>,
+    pub cloud_secret: Option<String>,
     pub provider: String,
     pub stack_code: String,
     #[serde(rename = "selected_plan")]
     pub selected_plan: String,
-    pub custom: forms::stack::Custom,
+    pub custom: Custom,
+    pub docker_compose: Option<Vec<u8>>,
 }
 
 impl TryFrom<&models::Stack> for Payload {
diff --git a/src/forms/stack/port.rs b/src/forms/stack/port.rs
index a6e77d23..0251d902 100644
--- a/src/forms/stack/port.rs
+++ b/src/forms/stack/port.rs
@@ -1,12 +1,18 @@
 use serde::{Deserialize, Serialize};
 use docker_compose_types as dctypes;
+use serde_valid::Validate;
 
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Port {
+    #[validate(custom(|v| validate_non_empty(v)))]
     pub host_port: Option<String>,
-    pub container_port: Option<String>,
+    #[validate(pattern = r"^\d{2,6}+$")]
+    pub container_port: String,
+    #[validate(enumerate("tcp", "udp"))]
+    pub protocol: Option<String>,
 }
 
+
 // impl Default for Port{
 //     fn default() -> Self {
 //         Port {
diff --git a/src/forms/stack/requirements.rs b/src/forms/stack/requirements.rs
index 02e6891b..402f80dd 100644
--- a/src/forms/stack/requirements.rs
+++ b/src/forms/stack/requirements.rs
@@ -5,16 +5,16 @@ use serde_valid::Validate;
 pub struct Requirements {
     #[validate(min_length = 1)]
     #[validate(max_length = 10)]
-    #[validate(pattern = r"^\d+\.?[0-9]+$")]
+    #[validate(pattern = r"^[0-9]*\.?[0-9]+$")]
     pub cpu: Option<String>,
     #[validate(min_length = 1)]
     #[validate(max_length = 10)]
-    #[validate(pattern = r"^\d+G$")]
+    #[validate(pattern = r"^[0-9]*\.?[0-9]+Gb?$")]
     #[serde(rename = "disk_size")]
     pub disk_size: Option<String>,
     #[serde(rename = "ram_size")]
     #[validate(min_length = 1)]
     #[validate(max_length = 10)]
-    #[validate(pattern = r"^\d+G$")]
+    #[validate(pattern = r"^[0-9]*\.?[0-9]+Gb?$")]
     pub ram_size: Option<String>,
 }
diff --git a/src/forms/stack1.rs b/src/forms/stack1.rs
new file mode 100644
index 00000000..22555fed
--- /dev/null
+++ b/src/forms/stack1.rs
@@ -0,0 +1,462 @@
+use serde::{Deserialize, Serialize};
+use serde_json::Value;
+use serde_valid::Validate;
+use std::fmt;
+use indexmap::IndexMap;
+use regex::Regex;
+use crate::helpers::dockerhub::DockerHub;
+use crate::helpers::stack::dctypes::SingleValue;
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct Role {
+    pub role: Option<Vec<String>>,
+}
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct Requirements {
+    #[validate(min_length = 1)]
+    #[validate(max_length = 10)]
+    #[validate(pattern = r"^[0-9]*\.?[0-9]+$")]
+    pub cpu: Option<String>,
+    #[validate(min_length = 1)]
+    #[validate(max_length = 10)]
+    #[validate(pattern = r"^[0-9]*\.?[0-9]+Gb?$")]
+    #[serde(rename = "disk_size")]
+    pub disk_size: Option<String>,
+    #[serde(rename = "ram_size")]
+    #[validate(min_length = 1)]
+    #[validate(max_length = 10)]
+    #[validate(pattern = r"^[0-9]*\.?[0-9]+Gb?$")]
+    pub ram_size: Option<String>,
+}
+
+fn validate_non_empty(v: &Option<String>) -> Result<(), serde_valid::validation::Error> {
+    if v.is_none() {
+        return Ok(());
+    }
+
+    if let Some(value) = v {
+        if value.is_empty() {
+            return Ok(());
+        }
+
+        // #[validate(pattern = r"^\d{2,6}+$")]
+        let re = Regex::new(r"^\d{2,6}+$").unwrap();
+
+        if !re.is_match(value.as_str()) {
+            return Err(serde_valid::validation::Error::Custom("Port is not valid.".to_owned()));
+        }
+    }
+
+    Ok(())
+}
+
+#[derive(Validate)]
+struct Data {
+    val: i32,
+}
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct Port {
+    #[validate(custom(|v| validate_non_empty(v)))]
+    pub host_port: Option<String>,
+    #[validate(pattern = r"^\d{2,6}+$")]
+    pub container_port: String,
+    #[validate(enumerate("tcp", "udp"))]
+    pub protocol: Option<String>,
+}
+
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct DockerImage {
+    // #[validate(min_length = 3)]
+    #[validate(max_length = 50)]
+    // @todo conditional check, if not empty
+    // #[validate(pattern = r"^[a-z0-9]+([-_.][a-z0-9]+)*$")]
+    pub dockerhub_user: Option<String>,
+    // #[validate(min_length = 3)]
+    #[validate(max_length = 50)]
+    // @todo conditional check, if not empty
+    // #[validate(pattern = r"^[a-z0-9]+([-_.][a-z0-9]+)*$")]
+    pub dockerhub_name: Option<String>,
+    // #[validate(min_length = 3)]
+    #[validate(max_length = 100)]
+    pub dockerhub_image: Option<String>,
+    pub dockerhub_password: Option<String>,
+}
+
+impl fmt::Display for DockerImage {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        let dh_image = self.dockerhub_image.as_ref().map(String::as_str).unwrap_or("");
+        let dh_nmspc = self.dockerhub_user.as_ref().map(String::as_str).unwrap_or("");
+        let dh_name = self.dockerhub_name.as_ref().map(String::as_str).unwrap_or("");
+
+        write!(
+            f,
+            "{}{}{}",
+            if !dh_nmspc.is_empty() { format!("{}/", dh_nmspc) } else { String::new() },
+            if !dh_name.is_empty() { dh_name } else { dh_image },
+            if !dh_name.contains(":") && dh_image.is_empty() { ":latest".to_string() } else { String::new() },
+        )
+    }
+}
+
+
+impl DockerImage {
+    #[tracing::instrument(name = "is_active")]
+    pub async fn is_active(&self) -> Result<bool, String> {
+        DockerHub::from(self).is_active().await
+    }
+}
+
+
+impl AsRef<DockerImage> for App {
+    fn as_ref(&self) -> &DockerImage {
+        &self.docker_image
+    }
+}
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct StackForm {
+    #[validate(max_length = 255)]
+    #[serde(rename = "commonDomain")]
+    pub common_domain: Option<String>,
+    pub domain_list: Option<DomainList>,
+    #[validate(min_length = 2)]
+    #[validate(max_length = 255)]
+    pub stack_code: Option<String>,
+    #[validate(min_length = 2)]
+    #[validate(max_length = 50)]
+    pub region: String,
+    #[validate(min_length = 1)]
+    #[validate(max_length = 50)]
+    pub zone: Option<String>,
+    #[validate(min_length = 2)]
+    #[validate(max_length = 50)]
+    pub server: String,
+    #[validate(min_length = 2)]
+    #[validate(max_length = 50)]
+    pub os: String,
+    #[validate(min_length = 3)]
+    #[validate(max_length = 50)]
+    pub ssl: String,
+    pub vars: Option<Vec<Var>>,
+    pub integrated_features: Option<Vec<Value>>,
+    pub extended_features: Option<Vec<Value>>,
+    pub subscriptions: Option<Vec<String>>,
+    pub form_app: Option<Vec<String>>,
+    #[validate(min_length = 3)]
+    #[validate(max_length = 50)]
+    pub disk_type: Option<String>,
+    pub save_token: bool,
+    #[validate(max_length = 255)]
+    pub cloud_token: Option<String>,
+    #[validate(max_length = 255)]
+    pub cloud_key: Option<String>,
+    #[validate(max_length = 255)]
+    pub cloud_secret: Option<String>,
+    #[validate(min_length = 2)]
+    #[validate(max_length = 50)]
+    pub provider: String,
+    #[validate(min_length = 3)]
+    #[validate(max_length = 50)]
+    pub selected_plan: String,
+    #[validate]
+    pub custom: Custom,
+}
+
+impl StackForm {
+    pub async fn is_readable_docker_image(&self) -> Result<bool, String> {
+        let mut is_active = true;
+        for app in &self.custom.web {
+            if !app.app.docker_image.is_active().await? {
+                is_active = false;
+                break;
+            }
+        }
+
+        // temporarily disabled
+        // if let Some(service) = &self.custom.service {
+        //     for app in service {
+        //         if !app.app.docker_image.is_active().await? {
+        //             is_active = false;
+        //             break;
+        //         }
+        //     }
+        // }
+        //
+        // if let Some(features) = &self.custom.feature {
+        //     for app in features {
+        //         if !app.app.docker_image.is_active().await? {
+        //             is_active = false;
+        //             break;
+        //         }
+        //     }
+        // }
+        Ok(is_active)
+    }
+}
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+#[serde(rename_all = "snake_case")]
+pub struct StackPayload {
+    pub(crate) id: Option<i32>,
+    pub(crate) user_token: Option<String>,
+    pub(crate) user_email: Option<String>,
+    #[serde(rename = "commonDomain")]
+    pub common_domain: String,
+    pub domain_list: Option<DomainList>,
+    pub region: String,
+    pub zone: Option<String>,
+    pub server: String,
+    pub os: String,
+    pub ssl: String,
+    pub vars: Option<Vec<Var>>,
+    #[serde(rename = "integrated_features")]
+    pub integrated_features: Option<Vec<Value>>,
+    #[serde(rename = "extended_features")]
+    pub extended_features: Option<Vec<Value>>,
+    pub subscriptions: Option<Vec<String>>,
+    pub form_app: Option<Vec<String>>,
+    pub disk_type: Option<String>,
+    pub save_token: bool,
+    pub cloud_token: Option<String>,
+    pub cloud_key: Option<String>,
+    pub cloud_secret: Option<String>,
+    pub provider: String,
+    pub stack_code: String,
+    #[serde(rename = "selected_plan")]
+    pub selected_plan: String,
+    pub custom: Custom,
+    pub docker_compose: Option<Vec<u8>>,
+}
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct DomainList {}
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Var {}
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct Price {
+    pub value: f64,
+}
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct Custom {
+    #[validate]
+    pub web: Vec<Web>,
+    #[validate]
+    pub feature: Option<Vec<Feature>>,
+    #[validate]
+    pub service: Option<Vec<Service>>,
+    #[validate(minimum = 0)]
+    #[validate(maximum = 10)]
+    pub servers_count: u32,
+    #[validate(min_length = 3)]
+    #[validate(max_length = 50)]
+    pub custom_stack_code: String,
+    #[validate(min_length = 3)]
+    #[validate(max_length = 255)]
+    pub project_git_url: Option<String>,
+    pub custom_stack_category: Option<Vec<String>>,
+    pub custom_stack_short_description: Option<String>,
+    pub custom_stack_description: Option<String>,
+    #[validate(min_length = 3)]
+    #[validate(max_length = 255)]
+    pub project_name: String,
+    pub project_overview: Option<String>,
+    pub project_description: Option<String>,
+    #[serde(flatten)]
+    pub networks: ComposeNetworks, // all networks
+}
+
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct App {
+    #[serde(rename = "_etag")]
+    #[validate(min_length = 3)]
+    #[validate(max_length = 255)]
+    pub etag: Option<String>,
+    #[serde(rename = "_id")]
+    pub id: String,
+    #[serde(rename = "_created")]
+    pub created: Option<String>,
+    #[serde(rename = "_updated")]
+    pub updated: Option<String>,
+    #[validate(min_length = 3)]
+    #[validate(max_length = 50)]
+    pub name: String,
+    #[validate(min_length = 3)]
+    #[validate(max_length = 50)]
+    pub code: String,
+    #[validate(min_length = 3)]
+    #[validate(max_length = 50)]
+    #[serde(rename = "type")]
+    pub type_field: String,
+    #[serde(flatten)]
+    pub role: Role,
+    pub default: Option<bool>,
+    pub versions: Option<Vec<Version>>,
+    #[serde(flatten)]
+    #[validate]
+    pub docker_image: DockerImage,
+    #[serde(flatten)]
+    #[validate]
+    pub requirements: Requirements,
+    #[validate(minimum = 1)]
+    pub popularity: Option<u32>,
+    pub commercial: Option<bool>,
+    pub subscription: Option<Value>,
+    pub autodeploy: Option<bool>,
+    pub suggested: Option<bool>,
+    pub dependency: Option<Value>,
+    pub avoid_render: Option<bool>,
+    pub price: Option<Price>,
+    pub icon: Option<Icon>,
+    pub domain: Option<String>,
+    pub category_id: Option<u32>,
+    pub parent_app_id: Option<u32>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub descr: Option<String>,
+    pub full_description: Option<String>,
+    pub description: Option<String>,
+    pub plan_type: Option<String>,
+    pub ansible_var: Option<String>,
+    pub repo_dir: Option<String>,
+    pub url_app: Option<String>,
+    pub url_git: Option<String>,
+    #[validate(enumerate("always", "no", "unless-stopped", "on-failure"))]
+    pub restart: String,
+    pub volumes: Option<Vec<Volume>>,
+    #[serde(flatten)]
+    pub environment: Environment,
+    #[serde(flatten)]
+    pub network: ServiceNetworks,
+    #[validate]
+    pub shared_ports: Option<Vec<Port>>,
+}
+
+
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct Volume {
+    pub host_path: Option<String>,
+    pub container_path: Option<String>,
+}
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct Volumes {
+    volumes: Vec<Volume>,
+}
+
+// pub(crate) type Networks = Option<Vec<String>>;
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct ServiceNetworks {
+    pub network: Option<Vec<String>>,
+}
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct NetworkDriver {
+    // not implemented
+}
+
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct Network {
+    pub(crate) id: String,
+    pub(crate) attachable: Option<bool>,
+    pub(crate) driver: Option<String>,
+    pub(crate) driver_opts: Option<NetworkDriver>,
+    pub(crate) enable_ipv6: Option<bool>,
+    pub(crate) internal: Option<bool>,
+    pub(crate) external: Option<bool>,
+    pub(crate) ipam: Option<String>,
+    pub(crate) labels: Option<String>,
+    pub(crate) name: String,
+}
+
+impl Default for Network {
+    fn default() -> Self {
+        // The case when we need at least one external network to be preconfigured
+        Network {
+            id: "default_network".to_string(),
+            attachable: None,
+            driver: None,
+            driver_opts: Default::default(),
+            enable_ipv6: None,
+            internal: None,
+            external: Some(true),
+            ipam: None,
+            labels: None,
+            name: "default_network".to_string(),
+        }
+    }
+}
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct ComposeNetworks {
+    pub networks: Option<Vec<Network>>,
+}
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct Web {
+    #[serde(flatten)]
+    #[validate]
+    pub app: App,
+    pub custom: Option<bool>,
+    pub main: Option<bool>,
+}
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct Feature {
+    #[serde(flatten)]
+    #[validate]
+    pub app: App,
+}
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct Service {
+    #[serde(flatten)]
+    #[validate]
+    pub(crate) app: App,
+}
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct Icon {
+    pub light: IconLight,
+    pub dark: IconDark,
+}
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct IconLight {
+    pub width: i64,
+    pub height: i64,
+    pub image: String,
+}
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct IconDark {}
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct Version {
+    #[serde(rename = "_etag")]
+    pub etag: Option<String>,
+    #[serde(rename = "_id")]
+    pub id: u32,
+    #[serde(rename = "_created")]
+    pub created: Option<String>,
+    #[serde(rename = "_updated")]
+    pub updated: Option<String>,
+    pub app_id: Option<u32>,
+    pub name: String,
+    #[validate(min_length = 3)]
+    #[validate(max_length = 20)]
+    pub version: String,
+    #[serde(rename = "update_status")]
+    pub update_status: Option<String>,
+    #[validate(min_length = 3)]
+    #[validate(max_length = 20)]
+    pub tag: String,
+}
diff --git a/src/helpers/json.rs b/src/helpers/json.rs
index 952d7680..ee209c10 100644
--- a/src/helpers/json.rs
+++ b/src/helpers/json.rs
@@ -74,6 +74,10 @@ where
         ErrorBadRequest(self.set_msg(msg).to_string())
     }
 
+    pub(crate) fn form_error(self, msg: String) -> Error {
+        ErrorBadRequest(msg)
+    }
+
     pub(crate) fn not_found<I: Into<String>>(self, msg: I) -> Error {
         ErrorNotFound(self.set_msg(msg).to_string())
     }
diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs
index 653c7adb..712ca5a6 100644
--- a/src/routes/stack/add.rs
+++ b/src/routes/stack/add.rs
@@ -25,7 +25,7 @@ pub async fn add(
     let form = body_into_form(body).await?;
     let stack_name = form.custom.custom_stack_code.clone();
 
-    check_if_stack_exists(pg_pool.get_ref(), &stack_name).await?;
+    stack_exists(pg_pool.get_ref(), &stack_name).await?;
 
     let body: Value = serde_json::to_value::<forms::stack::Stack>(form)
         .or(serde_json::to_value::<forms::stack::Stack>(forms::stack::Stack::default()))
@@ -40,8 +40,9 @@ pub async fn add(
         })
 }
 
-async fn check_if_stack_exists(pg_pool: &PgPool, stack_name: &String) -> Result<(), Error> {
-    db::stack::fetch_one_by_name(pg_pool, stack_name)
+
+async fn stack_exists(pool: &PgPool, stack_name: &String) -> Result<(), Error> {
+    db::stack::fetch_one_by_name(pool, stack_name)
         .await
         .map_err(|_| {
             JsonResponse::<models::Stack>::build().internal_server_error("Internal Server Error")
@@ -65,11 +66,11 @@ async fn body_into_form(body: Bytes) -> Result<forms::stack::Stack, Error> {
         })
         .and_then(|form: forms::stack::Stack| {
             if !form.validate().is_ok() {
-                let errors = form.validate().unwrap_err();
-                let err_msg = format!("Invalid data received {:?}", &errors.to_string());
+                let errors = form.validate().unwrap_err().to_string();
+                let err_msg = format!("Invalid data received {:?}", &errors);
                 tracing::debug!(err_msg);
 
-                return Err(JsonResponse::<models::Stack>::build().bad_request(errors.to_string()));
+                return Err(JsonResponse::<models::Stack>::build().form_error(errors));
             }
 
             Ok(form)
diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs
index 71d459ff..e3728531 100644
--- a/src/routes/stack/deploy.rs
+++ b/src/routes/stack/deploy.rs
@@ -8,6 +8,8 @@ use actix_web::{post, web, web::Data, Responder, Result};
 use lapin::publisher_confirm::Confirmation;
 use sqlx::PgPool;
 use std::sync::Arc;
+use crate::helpers::compressor::compress;
+
 
 #[tracing::instrument(name = "Deploy for every user. Admin endpoint")]
 #[post("/{id}/deploy")]
@@ -29,7 +31,7 @@ pub async fn add(
 
     let id = stack.id.clone();
     let dc = DcBuilder::new(stack);
-    dc.build().map_err(|err| {
+    let fc = dc.build().map_err(|err| {
         JsonResponse::<models::Stack>::build().internal_server_error(err)
     })?;
 
@@ -37,6 +39,8 @@ pub async fn add(
         .map_err(|err| JsonResponse::<models::Stack>::build().bad_request(err))?;
     stack_data.user_token = Some(user.id.clone());
     stack_data.user_email = Some(user.email.clone());
+    let compressed = fc.unwrap_or("".to_string());
+    stack_data.docker_compose = Some(compress(compressed.as_str()));
 
     mq_manager
         .publish_and_confirm(
diff --git a/src/routes/stack/update.rs b/src/routes/stack/update.rs
index ba4d7284..46ad014c 100644
--- a/src/routes/stack/update.rs
+++ b/src/routes/stack/update.rs
@@ -24,7 +24,7 @@ pub async fn update(
         .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(err))
         .and_then(|stack| match stack {
             Some(stack) => Ok(stack),
-            None => Err(JsonResponse::<models::Stack>::build().not_found("not found")),
+            None => Err(JsonResponse::<models::Stack>::build().not_found("Object not found")),
         })?;
 
     let stack_name = form.custom.custom_stack_code.clone();
@@ -32,7 +32,7 @@ pub async fn update(
     let user_id = user.id.clone();
 
     if let Err(errors) = form.validate() {
-        return Err(JsonResponse::<models::Stack>::build().bad_request(errors.to_string()));
+        return Err(JsonResponse::<models::Stack>::build().form_error(errors.unwrap_err().to_string()));
     }
 
     let form_inner = form.into_inner();
diff --git a/src/routes/test/deploy.rs b/src/routes/test/deploy.rs
index f123417a..4f36a3ae 100644
--- a/src/routes/test/deploy.rs
+++ b/src/routes/test/deploy.rs
@@ -1,8 +1,15 @@
 use crate::models::Client;
 use actix_web::{post, web, Responder, Result};
+use serde::Serialize;
 use std::sync::Arc;
 use crate::helpers::JsonResponse;
 
+#[derive(Serialize)]
+struct DeployResponse {
+    status: String,
+    client: Arc<Client>,
+}
+
 #[tracing::instrument(name = "Test deploy.")]
 #[post("/deploy")]
 pub async fn handler(client: web::ReqData<Arc<Client>>) -> Result<impl Responder> {

From b5574055984e144fe3cafbe24072fa5602c1da1b Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Sun, 25 Feb 2024 09:46:04 +0200
Subject: [PATCH 184/284] brotli compressor from issue-16

---
 Cargo.lock                 |  1 +
 Cargo.toml                 |  1 +
 src/forms/stack/payload.rs |  2 +-
 src/helpers/compressor.rs  | 20 ++++++++++++++++++++
 src/helpers/mod.rs         |  2 ++
 src/routes/stack/deploy.rs |  4 ++--
 src/routes/stack/update.rs |  2 +-
 7 files changed, 28 insertions(+), 4 deletions(-)
 create mode 100644 src/helpers/compressor.rs

diff --git a/Cargo.lock b/Cargo.lock
index df244b2a..0ea5e67f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3442,6 +3442,7 @@ dependencies = [
  "actix-http",
  "actix-web",
  "actix-web-httpauth",
+ "brotli",
  "chrono",
  "clap",
  "config",
diff --git a/Cargo.toml b/Cargo.toml
index 46425339..f817a308 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -54,6 +54,7 @@ serde_yaml = "0.9"
 lapin = { version = "2.3.1", features = ["serde_json"] }
 futures-lite = "1.13.0"
 clap = { version = "4.4.8", features = ["derive"] }
+brotli = "3.4.0"
 serde_path_to_error = "0.1.14"
 deadpool-lapin = "0.11.0"
 docker-compose-types = "0.7.0"
diff --git a/src/forms/stack/payload.rs b/src/forms/stack/payload.rs
index ddcd9276..f89b3c6a 100644
--- a/src/forms/stack/payload.rs
+++ b/src/forms/stack/payload.rs
@@ -35,7 +35,7 @@ pub struct Payload {
     pub stack_code: String,
     #[serde(rename = "selected_plan")]
     pub selected_plan: String,
-    pub custom: Custom,
+    pub custom: forms::stack::Custom,
     pub docker_compose: Option<Vec<u8>>,
 }
 
diff --git a/src/helpers/compressor.rs b/src/helpers/compressor.rs
new file mode 100644
index 00000000..624747d7
--- /dev/null
+++ b/src/helpers/compressor.rs
@@ -0,0 +1,20 @@
+use brotli::{CompressorWriter, Decompressor};
+use std::io::{Read, Write};
+
+pub fn compress(input: &str) -> Vec<u8> {
+    let mut compressed = Vec::new();
+    let mut compressor = CompressorWriter::new(
+        &mut compressed, 4096, 11, 22
+    );
+    compressor.write_all(input.as_bytes()).unwrap();
+    compressor.flush().unwrap();
+    drop(compressor);
+    compressed
+}
+
+pub fn decompress(input: &[u8]) -> String {
+    let mut decompressed = String::new();
+    let mut decompressor = Decompressor::new(input, 4096);
+    decompressor.read_to_string(&mut decompressed).unwrap();
+    decompressed
+}
\ No newline at end of file
diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs
index ad705350..17f75f10 100644
--- a/src/helpers/mod.rs
+++ b/src/helpers/mod.rs
@@ -6,4 +6,6 @@ pub(crate) mod stack;
 pub use json::*;
 pub use mq_manager::MqManager;
 pub mod dockerhub;
+pub(crate) mod compressor;
+
 pub use dockerhub::*;
diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs
index e3728531..a2bf8693 100644
--- a/src/routes/stack/deploy.rs
+++ b/src/routes/stack/deploy.rs
@@ -39,8 +39,8 @@ pub async fn add(
         .map_err(|err| JsonResponse::<models::Stack>::build().bad_request(err))?;
     stack_data.user_token = Some(user.id.clone());
     stack_data.user_email = Some(user.email.clone());
-    let compressed = fc.unwrap_or("".to_string());
-    stack_data.docker_compose = Some(compress(compressed.as_str()));
+    // let compressed = fc.unwrap_or("".to_string());
+    stack_data.docker_compose = Some(compress(fc.as_str()));
 
     mq_manager
         .publish_and_confirm(
diff --git a/src/routes/stack/update.rs b/src/routes/stack/update.rs
index 46ad014c..ef11d825 100644
--- a/src/routes/stack/update.rs
+++ b/src/routes/stack/update.rs
@@ -32,7 +32,7 @@ pub async fn update(
     let user_id = user.id.clone();
 
     if let Err(errors) = form.validate() {
-        return Err(JsonResponse::<models::Stack>::build().form_error(errors.unwrap_err().to_string()));
+        return Err(JsonResponse::<models::Stack>::build().form_error(errors.to_string()));
     }
 
     let form_inner = form.into_inner();

From c9b25fc9b70d471c2be116073d5b5fbc7b086400 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Sun, 25 Feb 2024 09:54:32 +0200
Subject: [PATCH 185/284] validate

---
 src/forms/stack/app.rs    | 5 +++--
 src/forms/stack/custom.rs | 3 +--
 src/forms/stack/web.rs    | 3 ++-
 3 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/src/forms/stack/app.rs b/src/forms/stack/app.rs
index 1eedd6b2..93529a37 100644
--- a/src/forms/stack/app.rs
+++ b/src/forms/stack/app.rs
@@ -132,15 +132,16 @@ impl App {
         };
 
         let mut envs = IndexMap::new();
-        for item in self.environment.environment.clone().unwrap_or_default() {
+        for item in self.environment.environment.clone() {
             let items = item
                 .into_iter()
-                .map(|(k, v)| (k, Some(dctypes::SingleValue::String(v.clone()))))
+                .map(|env_var| (env_var.key, Some(dctypes::SingleValue::String(env_var.value.clone()))))
                 .collect::<IndexMap<_, _>>();
 
             envs.extend(items);
         }
 
+
         service.ports = dctypes::Ports::Long(ports);
         service.restart = Some("always".to_owned());
         service.volumes = volumes;
diff --git a/src/forms/stack/custom.rs b/src/forms/stack/custom.rs
index b127a126..4e6bd871 100644
--- a/src/forms/stack/custom.rs
+++ b/src/forms/stack/custom.rs
@@ -1,10 +1,9 @@
 use serde::{Deserialize, Serialize};
-use serde_json::Value;
-use serde_valid::Validate;
 use crate::forms;
 use indexmap::IndexMap;
 use docker_compose_types as dctypes;
 use crate::forms::stack::Network;
+use serde_valid::Validate;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Custom {
diff --git a/src/forms/stack/web.rs b/src/forms/stack/web.rs
index 776ea0a1..4bef9bd0 100644
--- a/src/forms/stack/web.rs
+++ b/src/forms/stack/web.rs
@@ -1,7 +1,8 @@
 use serde::{Deserialize, Serialize};
+use serde_valid::Validate;
 use crate::forms::stack::*;
 
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Web {
     #[serde(flatten)]
     pub app: App,

From bca14b99c46f7d22f833bee6b40a0335e74ab0fa Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Sun, 25 Feb 2024 09:56:59 +0200
Subject: [PATCH 186/284] validate

---
 src/forms/stack/feature.rs |   3 +-
 src/forms/stack1.rs        | 462 -------------------------------------
 2 files changed, 2 insertions(+), 463 deletions(-)
 delete mode 100644 src/forms/stack1.rs

diff --git a/src/forms/stack/feature.rs b/src/forms/stack/feature.rs
index 8e5908c9..147aa033 100644
--- a/src/forms/stack/feature.rs
+++ b/src/forms/stack/feature.rs
@@ -1,7 +1,8 @@
 use serde::{Deserialize, Serialize};
+use serde_valid::Validate;
 use crate::forms::stack::*;
 
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Feature {
     // #[serde(rename(deserialize = "sharedPorts"))]
     // #[serde(rename(serialize = "shared_ports"))]
diff --git a/src/forms/stack1.rs b/src/forms/stack1.rs
deleted file mode 100644
index 22555fed..00000000
--- a/src/forms/stack1.rs
+++ /dev/null
@@ -1,462 +0,0 @@
-use serde::{Deserialize, Serialize};
-use serde_json::Value;
-use serde_valid::Validate;
-use std::fmt;
-use indexmap::IndexMap;
-use regex::Regex;
-use crate::helpers::dockerhub::DockerHub;
-use crate::helpers::stack::dctypes::SingleValue;
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct Role {
-    pub role: Option<Vec<String>>,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct Requirements {
-    #[validate(min_length = 1)]
-    #[validate(max_length = 10)]
-    #[validate(pattern = r"^[0-9]*\.?[0-9]+$")]
-    pub cpu: Option<String>,
-    #[validate(min_length = 1)]
-    #[validate(max_length = 10)]
-    #[validate(pattern = r"^[0-9]*\.?[0-9]+Gb?$")]
-    #[serde(rename = "disk_size")]
-    pub disk_size: Option<String>,
-    #[serde(rename = "ram_size")]
-    #[validate(min_length = 1)]
-    #[validate(max_length = 10)]
-    #[validate(pattern = r"^[0-9]*\.?[0-9]+Gb?$")]
-    pub ram_size: Option<String>,
-}
-
-fn validate_non_empty(v: &Option<String>) -> Result<(), serde_valid::validation::Error> {
-    if v.is_none() {
-        return Ok(());
-    }
-
-    if let Some(value) = v {
-        if value.is_empty() {
-            return Ok(());
-        }
-
-        // #[validate(pattern = r"^\d{2,6}+$")]
-        let re = Regex::new(r"^\d{2,6}+$").unwrap();
-
-        if !re.is_match(value.as_str()) {
-            return Err(serde_valid::validation::Error::Custom("Port is not valid.".to_owned()));
-        }
-    }
-
-    Ok(())
-}
-
-#[derive(Validate)]
-struct Data {
-    val: i32,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct Port {
-    #[validate(custom(|v| validate_non_empty(v)))]
-    pub host_port: Option<String>,
-    #[validate(pattern = r"^\d{2,6}+$")]
-    pub container_port: String,
-    #[validate(enumerate("tcp", "udp"))]
-    pub protocol: Option<String>,
-}
-
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct DockerImage {
-    // #[validate(min_length = 3)]
-    #[validate(max_length = 50)]
-    // @todo conditional check, if not empty
-    // #[validate(pattern = r"^[a-z0-9]+([-_.][a-z0-9]+)*$")]
-    pub dockerhub_user: Option<String>,
-    // #[validate(min_length = 3)]
-    #[validate(max_length = 50)]
-    // @todo conditional check, if not empty
-    // #[validate(pattern = r"^[a-z0-9]+([-_.][a-z0-9]+)*$")]
-    pub dockerhub_name: Option<String>,
-    // #[validate(min_length = 3)]
-    #[validate(max_length = 100)]
-    pub dockerhub_image: Option<String>,
-    pub dockerhub_password: Option<String>,
-}
-
-impl fmt::Display for DockerImage {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let dh_image = self.dockerhub_image.as_ref().map(String::as_str).unwrap_or("");
-        let dh_nmspc = self.dockerhub_user.as_ref().map(String::as_str).unwrap_or("");
-        let dh_name = self.dockerhub_name.as_ref().map(String::as_str).unwrap_or("");
-
-        write!(
-            f,
-            "{}{}{}",
-            if !dh_nmspc.is_empty() { format!("{}/", dh_nmspc) } else { String::new() },
-            if !dh_name.is_empty() { dh_name } else { dh_image },
-            if !dh_name.contains(":") && dh_image.is_empty() { ":latest".to_string() } else { String::new() },
-        )
-    }
-}
-
-
-impl DockerImage {
-    #[tracing::instrument(name = "is_active")]
-    pub async fn is_active(&self) -> Result<bool, String> {
-        DockerHub::from(self).is_active().await
-    }
-}
-
-
-impl AsRef<DockerImage> for App {
-    fn as_ref(&self) -> &DockerImage {
-        &self.docker_image
-    }
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct StackForm {
-    #[validate(max_length = 255)]
-    #[serde(rename = "commonDomain")]
-    pub common_domain: Option<String>,
-    pub domain_list: Option<DomainList>,
-    #[validate(min_length = 2)]
-    #[validate(max_length = 255)]
-    pub stack_code: Option<String>,
-    #[validate(min_length = 2)]
-    #[validate(max_length = 50)]
-    pub region: String,
-    #[validate(min_length = 1)]
-    #[validate(max_length = 50)]
-    pub zone: Option<String>,
-    #[validate(min_length = 2)]
-    #[validate(max_length = 50)]
-    pub server: String,
-    #[validate(min_length = 2)]
-    #[validate(max_length = 50)]
-    pub os: String,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 50)]
-    pub ssl: String,
-    pub vars: Option<Vec<Var>>,
-    pub integrated_features: Option<Vec<Value>>,
-    pub extended_features: Option<Vec<Value>>,
-    pub subscriptions: Option<Vec<String>>,
-    pub form_app: Option<Vec<String>>,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 50)]
-    pub disk_type: Option<String>,
-    pub save_token: bool,
-    #[validate(max_length = 255)]
-    pub cloud_token: Option<String>,
-    #[validate(max_length = 255)]
-    pub cloud_key: Option<String>,
-    #[validate(max_length = 255)]
-    pub cloud_secret: Option<String>,
-    #[validate(min_length = 2)]
-    #[validate(max_length = 50)]
-    pub provider: String,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 50)]
-    pub selected_plan: String,
-    #[validate]
-    pub custom: Custom,
-}
-
-impl StackForm {
-    pub async fn is_readable_docker_image(&self) -> Result<bool, String> {
-        let mut is_active = true;
-        for app in &self.custom.web {
-            if !app.app.docker_image.is_active().await? {
-                is_active = false;
-                break;
-            }
-        }
-
-        // temporarily disabled
-        // if let Some(service) = &self.custom.service {
-        //     for app in service {
-        //         if !app.app.docker_image.is_active().await? {
-        //             is_active = false;
-        //             break;
-        //         }
-        //     }
-        // }
-        //
-        // if let Some(features) = &self.custom.feature {
-        //     for app in features {
-        //         if !app.app.docker_image.is_active().await? {
-        //             is_active = false;
-        //             break;
-        //         }
-        //     }
-        // }
-        Ok(is_active)
-    }
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-#[serde(rename_all = "snake_case")]
-pub struct StackPayload {
-    pub(crate) id: Option<i32>,
-    pub(crate) user_token: Option<String>,
-    pub(crate) user_email: Option<String>,
-    #[serde(rename = "commonDomain")]
-    pub common_domain: String,
-    pub domain_list: Option<DomainList>,
-    pub region: String,
-    pub zone: Option<String>,
-    pub server: String,
-    pub os: String,
-    pub ssl: String,
-    pub vars: Option<Vec<Var>>,
-    #[serde(rename = "integrated_features")]
-    pub integrated_features: Option<Vec<Value>>,
-    #[serde(rename = "extended_features")]
-    pub extended_features: Option<Vec<Value>>,
-    pub subscriptions: Option<Vec<String>>,
-    pub form_app: Option<Vec<String>>,
-    pub disk_type: Option<String>,
-    pub save_token: bool,
-    pub cloud_token: Option<String>,
-    pub cloud_key: Option<String>,
-    pub cloud_secret: Option<String>,
-    pub provider: String,
-    pub stack_code: String,
-    #[serde(rename = "selected_plan")]
-    pub selected_plan: String,
-    pub custom: Custom,
-    pub docker_compose: Option<Vec<u8>>,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct DomainList {}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct Var {}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct Price {
-    pub value: f64,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct Custom {
-    #[validate]
-    pub web: Vec<Web>,
-    #[validate]
-    pub feature: Option<Vec<Feature>>,
-    #[validate]
-    pub service: Option<Vec<Service>>,
-    #[validate(minimum = 0)]
-    #[validate(maximum = 10)]
-    pub servers_count: u32,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 50)]
-    pub custom_stack_code: String,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 255)]
-    pub project_git_url: Option<String>,
-    pub custom_stack_category: Option<Vec<String>>,
-    pub custom_stack_short_description: Option<String>,
-    pub custom_stack_description: Option<String>,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 255)]
-    pub project_name: String,
-    pub project_overview: Option<String>,
-    pub project_description: Option<String>,
-    #[serde(flatten)]
-    pub networks: ComposeNetworks, // all networks
-}
-
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct App {
-    #[serde(rename = "_etag")]
-    #[validate(min_length = 3)]
-    #[validate(max_length = 255)]
-    pub etag: Option<String>,
-    #[serde(rename = "_id")]
-    pub id: String,
-    #[serde(rename = "_created")]
-    pub created: Option<String>,
-    #[serde(rename = "_updated")]
-    pub updated: Option<String>,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 50)]
-    pub name: String,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 50)]
-    pub code: String,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 50)]
-    #[serde(rename = "type")]
-    pub type_field: String,
-    #[serde(flatten)]
-    pub role: Role,
-    pub default: Option<bool>,
-    pub versions: Option<Vec<Version>>,
-    #[serde(flatten)]
-    #[validate]
-    pub docker_image: DockerImage,
-    #[serde(flatten)]
-    #[validate]
-    pub requirements: Requirements,
-    #[validate(minimum = 1)]
-    pub popularity: Option<u32>,
-    pub commercial: Option<bool>,
-    pub subscription: Option<Value>,
-    pub autodeploy: Option<bool>,
-    pub suggested: Option<bool>,
-    pub dependency: Option<Value>,
-    pub avoid_render: Option<bool>,
-    pub price: Option<Price>,
-    pub icon: Option<Icon>,
-    pub domain: Option<String>,
-    pub category_id: Option<u32>,
-    pub parent_app_id: Option<u32>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub descr: Option<String>,
-    pub full_description: Option<String>,
-    pub description: Option<String>,
-    pub plan_type: Option<String>,
-    pub ansible_var: Option<String>,
-    pub repo_dir: Option<String>,
-    pub url_app: Option<String>,
-    pub url_git: Option<String>,
-    #[validate(enumerate("always", "no", "unless-stopped", "on-failure"))]
-    pub restart: String,
-    pub volumes: Option<Vec<Volume>>,
-    #[serde(flatten)]
-    pub environment: Environment,
-    #[serde(flatten)]
-    pub network: ServiceNetworks,
-    #[validate]
-    pub shared_ports: Option<Vec<Port>>,
-}
-
-
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct Volume {
-    pub host_path: Option<String>,
-    pub container_path: Option<String>,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct Volumes {
-    volumes: Vec<Volume>,
-}
-
-// pub(crate) type Networks = Option<Vec<String>>;
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct ServiceNetworks {
-    pub network: Option<Vec<String>>,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct NetworkDriver {
-    // not implemented
-}
-
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct Network {
-    pub(crate) id: String,
-    pub(crate) attachable: Option<bool>,
-    pub(crate) driver: Option<String>,
-    pub(crate) driver_opts: Option<NetworkDriver>,
-    pub(crate) enable_ipv6: Option<bool>,
-    pub(crate) internal: Option<bool>,
-    pub(crate) external: Option<bool>,
-    pub(crate) ipam: Option<String>,
-    pub(crate) labels: Option<String>,
-    pub(crate) name: String,
-}
-
-impl Default for Network {
-    fn default() -> Self {
-        // The case when we need at least one external network to be preconfigured
-        Network {
-            id: "default_network".to_string(),
-            attachable: None,
-            driver: None,
-            driver_opts: Default::default(),
-            enable_ipv6: None,
-            internal: None,
-            external: Some(true),
-            ipam: None,
-            labels: None,
-            name: "default_network".to_string(),
-        }
-    }
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct ComposeNetworks {
-    pub networks: Option<Vec<Network>>,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct Web {
-    #[serde(flatten)]
-    #[validate]
-    pub app: App,
-    pub custom: Option<bool>,
-    pub main: Option<bool>,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct Feature {
-    #[serde(flatten)]
-    #[validate]
-    pub app: App,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct Service {
-    #[serde(flatten)]
-    #[validate]
-    pub(crate) app: App,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct Icon {
-    pub light: IconLight,
-    pub dark: IconDark,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct IconLight {
-    pub width: i64,
-    pub height: i64,
-    pub image: String,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct IconDark {}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct Version {
-    #[serde(rename = "_etag")]
-    pub etag: Option<String>,
-    #[serde(rename = "_id")]
-    pub id: u32,
-    #[serde(rename = "_created")]
-    pub created: Option<String>,
-    #[serde(rename = "_updated")]
-    pub updated: Option<String>,
-    pub app_id: Option<u32>,
-    pub name: String,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 20)]
-    pub version: String,
-    #[serde(rename = "update_status")]
-    pub update_status: Option<String>,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 20)]
-    pub tag: String,
-}

From de24ab9a7cd1ed94ebd6ea72e81b65bb93d3c013 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Sun, 25 Feb 2024 09:59:20 +0200
Subject: [PATCH 187/284] validate_non_empty added

---
 src/forms/stack/port.rs | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/src/forms/stack/port.rs b/src/forms/stack/port.rs
index 0251d902..5fa5c389 100644
--- a/src/forms/stack/port.rs
+++ b/src/forms/stack/port.rs
@@ -1,5 +1,6 @@
 use serde::{Deserialize, Serialize};
 use docker_compose_types as dctypes;
+use regex::Regex;
 use serde_valid::Validate;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
@@ -12,6 +13,27 @@ pub struct Port {
     pub protocol: Option<String>,
 }
 
+fn validate_non_empty(v: &Option<String>) -> Result<(), serde_valid::validation::Error> {
+    if v.is_none() {
+        return Ok(());
+    }
+
+    if let Some(value) = v {
+        if value.is_empty() {
+            return Ok(());
+        }
+
+        let re = Regex::new(r"^\d{2,6}$").unwrap();
+
+        if !re.is_match(value.as_str()) {
+            return Err(serde_valid::validation::Error::Custom("Port is not valid.".to_owned()));
+        }
+    }
+
+    Ok(())
+}
+
+
 
 // impl Default for Port{
 //     fn default() -> Self {

From 5bfda15fffc746a90d51bda4aac4a974ddb9cd12 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Sun, 25 Feb 2024 10:06:48 +0200
Subject: [PATCH 188/284] TryInto<dctypes::Port> fix

---
 src/forms/stack/port.rs | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/src/forms/stack/port.rs b/src/forms/stack/port.rs
index 5fa5c389..4da0c497 100644
--- a/src/forms/stack/port.rs
+++ b/src/forms/stack/port.rs
@@ -50,11 +50,10 @@ fn validate_non_empty(v: &Option<String>) -> Result<(), serde_valid::validation:
 impl TryInto<dctypes::Port> for &Port {
     type Error = String;
     fn try_into(self) -> Result<dctypes::Port, Self::Error> {
-        let cp = self
-            .container_port
-            .as_ref()
-            .map_or(Ok(0u16), |s| s.parse::<u16>())
-            .map_err(|_| "Could not parse container port".to_string())?;
+        let cp = self.container_port
+            .clone()
+            .parse::<u16>()
+            .map_err(|err| "Could not parse container port".to_string() )?;
 
         let hp = match self.host_port.clone() {
             Some(hp) => {

From 40cfee23f3ba03b1b8f62bd11a05cc55108e5abd Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Sun, 25 Feb 2024 18:14:53 +0200
Subject: [PATCH 189/284] field main is removed

---
 src/forms/stack/web.rs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/forms/stack/web.rs b/src/forms/stack/web.rs
index 4bef9bd0..87e70cff 100644
--- a/src/forms/stack/web.rs
+++ b/src/forms/stack/web.rs
@@ -7,5 +7,4 @@ pub struct Web {
     #[serde(flatten)]
     pub app: App,
     pub custom: Option<bool>,
-    pub main: bool,
 }

From 4f31021953a0dec292902f9b1d3acdbb5f57918b Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Wed, 28 Feb 2024 10:17:35 +0200
Subject: [PATCH 190/284] DockerImage fmt logic from issue-16

---
 src/forms/rating.rs             |  6 +--
 src/forms/stack/docker_image.rs | 17 ++++----
 src/helpers/dockerhub.rs        | 71 ++++++++++++++-------------------
 3 files changed, 41 insertions(+), 53 deletions(-)

diff --git a/src/forms/rating.rs b/src/forms/rating.rs
index d7a4bd7e..04b9b6b7 100644
--- a/src/forms/rating.rs
+++ b/src/forms/rating.rs
@@ -15,12 +15,12 @@ pub struct Rating {
 
 impl Into<models::Rating> for Rating {
     fn into(self) -> models::Rating {
-        let mut rating = models::Rating::default(); 
+        let mut rating = models::Rating::default();
         rating.obj_id = self.obj_id;
         rating.category = self.category.into(); //todo change the type of category field to the RateCategory
-        rating.hidden = Some(false); 
+        rating.hidden = Some(false);
         rating.rate = Some(self.rate);
-        rating.comment = self.comment; 
+        rating.comment = self.comment;
 
         rating
     }
diff --git a/src/forms/stack/docker_image.rs b/src/forms/stack/docker_image.rs
index 85a335a7..e9737d0b 100644
--- a/src/forms/stack/docker_image.rs
+++ b/src/forms/stack/docker_image.rs
@@ -24,21 +24,20 @@ pub struct DockerImage {
 
 impl fmt::Display for DockerImage {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let tag = "latest";
+        let dh_image = self.dockerhub_image.as_ref().map(String::as_str).unwrap_or("");
+        let dh_nmspc = self.dockerhub_user.as_ref().map(String::as_str).unwrap_or("");
+        let dh_name = self.dockerhub_name.as_ref().map(String::as_str).unwrap_or("");
 
-        let dim = self.dockerhub_image.clone().unwrap_or("".to_string());
         write!(
             f,
-            "{}/{}:{}",
-            self.dockerhub_user
-                .clone()
-                .unwrap_or("trydirect".to_string())
-                .clone(),
-            self.dockerhub_name.clone().unwrap_or(dim),
-            tag
+            "{}{}{}",
+            if !dh_nmspc.is_empty() { format!("{}/", dh_nmspc) } else { String::new() },
+            if !dh_name.is_empty() { dh_name } else { dh_image },
+            if !dh_name.contains(":") && dh_image.is_empty() { ":latest".to_string() } else { String::new() },
         )
     }
 }
+
 impl DockerImage {
     #[tracing::instrument(name = "is_active")]
     pub async fn is_active(&self) -> Result<bool, String> {
diff --git a/src/helpers/dockerhub.rs b/src/helpers/dockerhub.rs
index a4af31c8..e3f17207 100644
--- a/src/helpers/dockerhub.rs
+++ b/src/helpers/dockerhub.rs
@@ -1,19 +1,18 @@
+use crate::forms::stack::DockerImage;
 use reqwest::RequestBuilder;
 use serde_derive::{Deserialize, Serialize};
-use serde_valid::Validate;
 use serde_json::Value;
-use crate::forms::stack::DockerImage;
-
+use serde_valid::Validate;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct DockerHubToken {
-    pub token: Option<String>
+    pub token: Option<String>,
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct DockerHubCreds<'a> {
     pub(crate) username: &'a str,
-    pub(crate) password: &'a str
+    pub(crate) password: &'a str,
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
@@ -48,14 +47,14 @@ struct Tag {
     tag_last_pulled: Option<String>,
     tag_last_pushed: Option<String>,
     tag_status: String,
-    v2: bool
+    v2: bool,
 }
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 struct TagResult {
     pub count: Option<i64>,
     next: Option<Value>,
     previous: Option<Value>,
-    results: Vec<Tag>
+    results: Vec<Tag>,
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
@@ -92,7 +91,6 @@ pub struct DockerHub<'a> {
 }
 
 impl<'a> DockerHub<'a> {
-
     pub async fn login(&'a self) -> Result<DockerHubToken, String> {
         let endpoint = "https://hub.docker.com/v2/users/login";
 
@@ -128,9 +126,10 @@ impl<'a> DockerHub<'a> {
                 tracing::debug!("Get public image repositories response {:?}", repositories);
                 if repositories.count.unwrap_or(0) > 0 {
                     // let's find at least one active tag
-                    let active = repositories.results
+                    let active = repositories
+                        .results
                         .into_iter()
-                        .any(|mut repo| repo.status == 1 );
+                        .any(|repo| repo.status == 1);
                     active
                 } else {
                     false
@@ -139,9 +138,10 @@ impl<'a> DockerHub<'a> {
     }
 
     pub async fn lookup_private_repo(&self) -> Result<bool, String> {
-        let url = format!("https://hub.docker.com/v2/namespaces/{}/repositories/{}/tags",
-                          &self.creds.username,
-                          &self.repos);
+        let url = format!(
+            "https://hub.docker.com/v2/namespaces/{}/repositories/{}/tags",
+            &self.creds.username, &self.repos
+        );
         tracing::debug!("Validate image {:?}", url);
         let client = reqwest::Client::new()
             .get(url)
@@ -161,9 +161,10 @@ impl<'a> DockerHub<'a> {
                 tracing::debug!("Validate private image response {:?}", tags);
                 if tags.count.unwrap_or(0) > 0 {
                     // let's find at least one active tag
-                    let active = tags.results
+                    let active = tags
+                        .results
                         .into_iter()
-                        .any(|tag| tag.tag_status.contains("active") );
+                        .any(|tag| tag.tag_status.contains("active"));
                     active
                 } else {
                     false
@@ -171,66 +172,54 @@ impl<'a> DockerHub<'a> {
             })
     }
     pub async fn is_active(&'a self) -> Result<bool, String> {
-
         // if namespace/user is not set change endpoint and return a different response
         if self.creds.username.is_empty() {
             match self.lookup_public_repo().await {
-               Ok(result) => Ok(result),
-               Err(_e) => Ok(false)
+                Ok(result) => Ok(result),
+                Err(_e) => Ok(false),
             }
-        } else{
+        } else {
             match self.lookup_private_repo().await {
                 Ok(result) => Ok(result),
-                Err(_e) => Ok(false)
+                Err(_e) => Ok(false),
             }
         }
     }
 
     pub async fn set_token(&self, mut client: RequestBuilder) -> Result<RequestBuilder, String> {
-
         if self.creds.password.is_empty() {
             tracing::debug!("Password is empty. Image should be public");
             return Ok(client);
-        } else{
-
+        } else {
         }
         tracing::debug!("Password is set. Login..");
         let token = self.login().await?;
 
         match token.token {
-            None => {
-                Ok(client)
-            },
-            Some(token) => {
-                Ok(client.bearer_auth(token))
-            }
+            None => Ok(client),
+            Some(token) => Ok(client.bearer_auth(token)),
         }
     }
 }
 
-impl<'a> From <&'a DockerImage> for DockerHub<'a> {
-
+impl<'a> From<&'a DockerImage> for DockerHub<'a> {
     fn from(image: &'a DockerImage) -> Self {
         let username = match image.dockerhub_user {
-            Some(ref username) => {
-                username
-            },
-            None => ""
+            Some(ref username) => username,
+            None => "",
         };
         let password = match image.dockerhub_password {
-            Some(ref password) => {
-                password
-            },
-            None => ""
+            Some(ref password) => password,
+            None => "",
         };
 
         DockerHub {
             creds: DockerHubCreds {
                 username: username,
-                password: password
+                password: password,
             },
             repos: image.dockerhub_name.clone().unwrap_or("".to_string()),
             image: format!("{}", image),
         }
     }
-}
\ No newline at end of file
+}

From 441549c0d7ba931713b9ef364ba1e648325f4246 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Wed, 28 Feb 2024 13:00:43 +0200
Subject: [PATCH 191/284] custom role for any custom stack

---
 docker-compose.yml         | 2 +-
 src/forms/stack/feature.rs | 1 +
 src/forms/stack/service.rs | 1 +
 3 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/docker-compose.yml b/docker-compose.yml
index 9a66dde3..467836ef 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -8,7 +8,7 @@ volumes:
 services:
 
   stacker:
-    image: trydirect/stacker:0.0.6
+    image: trydirect/stacker:0.0.7
     build: .
     container_name: stacker
     restart: always
diff --git a/src/forms/stack/feature.rs b/src/forms/stack/feature.rs
index 147aa033..b6d0cbcb 100644
--- a/src/forms/stack/feature.rs
+++ b/src/forms/stack/feature.rs
@@ -10,4 +10,5 @@ pub struct Feature {
     // pub shared_ports: Option<Vec<Port>>,
     #[serde(flatten)]
     pub app: App,
+    pub custom: Option<bool>,
 }
diff --git a/src/forms/stack/service.rs b/src/forms/stack/service.rs
index 1470233e..5aea4a8b 100644
--- a/src/forms/stack/service.rs
+++ b/src/forms/stack/service.rs
@@ -10,4 +10,5 @@ pub struct Service {
     // pub shared_ports: Option<Vec<Port>>,
     #[serde(flatten)]
     pub(crate) app: App,
+    pub custom: Option<bool>,
 }

From 046425cb5d3315bebbcc24634b43597ab972f8b4 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Wed, 28 Feb 2024 17:09:36 +0200
Subject: [PATCH 192/284] 30-access-policies

---
 src/routes/client/enable.rs   | 5 +++--
 src/startup.rs                | 6 ++++++
 tests/middleware_trydirect.rs | 4 ----
 3 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/src/routes/client/enable.rs b/src/routes/client/enable.rs
index ca36451c..c390a4ef 100644
--- a/src/routes/client/enable.rs
+++ b/src/routes/client/enable.rs
@@ -1,6 +1,6 @@
 use crate::configuration::Settings;
 use crate::db;
-use crate::helpers::client;
+use crate::helpers;
 use crate::helpers::JsonResponse;
 use crate::models;
 use actix_web::{put, web, Responder, Result};
@@ -16,6 +16,7 @@ pub async fn enable_handler(
     pg_pool: web::Data<PgPool>,
     path: web::Path<(i32,)>,
 ) -> Result<impl Responder> {
+    println!("user.id {}", user.id);
     //todo the owner
     //todo add admin endpoint
     let client_id = path.0;
@@ -28,7 +29,7 @@ pub async fn enable_handler(
         return Err(JsonResponse::<models::Client>::build().bad_request("client is already active"));
     }
 
-    client.secret = client::generate_secret(pg_pool.get_ref(), 255)
+    client.secret = helpers::client::generate_secret(pg_pool.get_ref(), 255)
         .await
         .map(|secret| Some(secret))
         .map_err(|err| JsonResponse::<models::Client>::build().bad_request(err))?;
diff --git a/src/startup.rs b/src/startup.rs
index 5e57dc85..5f0b30b8 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -41,6 +41,12 @@ pub async fn run(
                     .service(routes::client::enable_handler)
                     .service(routes::client::disable_handler),
             )
+            /*
+            .service(
+                web::scope("/admin/client")
+                    .service(routes::client::admin_enable_handler)
+            )
+            */
             .service(
                 web::scope("/test").service(routes::test::deploy::handler),
             )
diff --git a/tests/middleware_trydirect.rs b/tests/middleware_trydirect.rs
index 05906181..49377813 100644
--- a/tests/middleware_trydirect.rs
+++ b/tests/middleware_trydirect.rs
@@ -22,8 +22,4 @@ async fn middleware_trydirect_works() {
 
     assert!(response.status().is_success());
     assert_eq!(Some(0), response.content_length());
-
-
-    //todo header stacker-id not found
-    //
 }

From 5a510846c1e445a125115fdcdb736255b3dd06bc Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Thu, 29 Feb 2024 14:30:39 +0200
Subject: [PATCH 193/284] refactoring. renamed user_stack with project, cloud
 and server endpoins

---
 README.md                                     |  10 +-
 ...230903063840_creating_rating_tables.up.sql |   2 +-
 ...30905145525_creating_stack_tables.down.sql |   2 +-
 ...0230905145525_creating_stack_tables.up.sql |   9 +-
 ...240228125751_creating_deployments.down.sql |   2 +
 ...20240228125751_creating_deployments.up.sql |  14 ++
 .../20240229072555_creating_cloud.down.sql    |   2 +
 .../20240229072555_creating_cloud.up.sql      |  14 ++
 ...reating_user_stack_cloud_relation.down.sql |   2 +
 ..._creating_user_stack_cloud_relation.up.sql |   3 +
 ...40229080559_creating_cloud_server.down.sql |   3 +
 ...0240229080559_creating_cloud_server.up.sql |  22 ++
 ...eating_user_stack_server_relation.down.sql |   2 +
 ...creating_user_stack_server_relation.up.sql |   2 +
 src/db/cloud.rs                               | 120 +++++++++++
 src/db/deployment.rs                          |  68 ++++++
 src/db/mod.rs                                 |   5 +-
 src/db/project.rs                             | 194 ++++++++++++++++++
 src/db/server.rs                              | 152 ++++++++++++++
 src/db/stack.rs                               | 140 -------------
 src/forms/cloud.rs                            |  34 +++
 src/forms/mod.rs                              |   4 +-
 src/forms/{stack => project}/app.rs           |  26 +--
 .../{stack => project}/compose_networks.rs    |   4 +-
 src/forms/{stack => project}/custom.rs        |  16 +-
 src/forms/{stack => project}/docker_image.rs  |   0
 src/forms/{stack => project}/domain_list.rs   |   0
 src/forms/{stack => project}/environment.rs   |   0
 src/forms/{stack => project}/feature.rs       |   2 +-
 src/forms/{stack => project}/form.rs          |  39 +---
 src/forms/{stack => project}/icon.rs          |   2 +-
 src/forms/{stack => project}/icon_dark.rs     |   0
 src/forms/{stack => project}/icon_light.rs    |   0
 src/forms/{stack => project}/mod.rs           |   0
 src/forms/{stack => project}/network.rs       |   4 +-
 .../{stack => project}/network_driver.rs      |   0
 src/forms/{stack => project}/payload.rs       |  32 ++-
 src/forms/{stack => project}/port.rs          |   0
 src/forms/{stack => project}/price.rs         |   0
 src/forms/{stack => project}/requirements.rs  |   0
 src/forms/{stack => project}/role.rs          |   0
 src/forms/{stack => project}/service.rs       |   2 +-
 .../{stack => project}/service_networks.rs    |   2 +-
 src/forms/{stack => project}/var.rs           |   0
 src/forms/{stack => project}/version.rs       |   0
 src/forms/{stack => project}/volume.rs        |   4 +-
 src/forms/{stack => project}/volumes.rs       |   2 +-
 src/forms/{stack => project}/web.rs           |   2 +-
 src/forms/server.rs                           |  36 ++++
 src/helpers/dockerhub.rs                      |   2 +-
 src/helpers/mod.rs                            |   2 +-
 src/helpers/{stack => project}/builder.rs     |  14 +-
 .../{stack => project}/builder_config.rs      |   0
 .../dctypes/advanced_build_step.rs            |   2 +-
 .../dctypes/advanced_network_settings.rs      |   0
 .../dctypes/advanced_networks.rs              |   2 +-
 .../{stack => project}/dctypes/build_args.rs  |   0
 .../{stack => project}/dctypes/build_step.rs  |   2 +-
 .../{stack => project}/dctypes/compose.rs     |   2 +-
 .../dctypes/compose_file.rs                   |   2 +-
 .../dctypes/compose_network.rs                |   2 +-
 .../compose_network_setting_details.rs        |   0
 .../dctypes/compose_networks.rs               |   2 +-
 .../dctypes/compose_volume.rs                 |   5 +-
 .../dctypes/depends_condition.rs              |   0
 .../dctypes/depends_on_options.rs             |   2 +-
 .../{stack => project}/dctypes/env_file.rs    |   0
 .../{stack => project}/dctypes/environment.rs |   2 +-
 .../{stack => project}/dctypes/extension.rs   |   2 +-
 .../dctypes/extension_parse_error.rs          |   0
 .../dctypes/external_network_setting_bool.rs  |   0
 .../dctypes/external_volume.rs                |   0
 .../{stack => project}/dctypes/ipam.rs        |   2 +-
 .../{stack => project}/dctypes/ipam_config.rs |   0
 .../{stack => project}/dctypes/labels.rs      |   0
 .../dctypes/logging_parameters.rs             |   2 +-
 src/helpers/{stack => project}/dctypes/mod.rs |   2 +-
 .../dctypes/network_settings.rs               |   2 +-
 .../{stack => project}/dctypes/networks.rs    |   2 +-
 .../{stack => project}/dctypes/port.rs        |   4 +-
 .../{stack => project}/dctypes/ports.rs       |   2 +-
 .../dctypes/published_port.rs                 |   0
 .../{stack => project}/dctypes/service.rs     |   2 +-
 .../{stack => project}/dctypes/services.rs    |   2 +-
 .../dctypes/single_service.rs                 |   2 +-
 .../{stack => project}/dctypes/sys_ctls.rs    |   2 +-
 .../{stack => project}/dctypes/tmpfs.rs       |   0
 .../dctypes/top_level_volumes.rs              |   2 +-
 .../{stack => project}/dctypes/ulimit.rs      |   0
 .../{stack => project}/dctypes/ulimits.rs     |   2 +-
 src/helpers/{stack => project}/mod.rs         |   0
 src/models/cloud.rs                           |  15 ++
 src/models/deployment.rs                      |  38 ++++
 src/models/mod.rs                             |  10 +-
 src/models/product.rs                         |   6 +-
 src/models/{stack.rs => project.rs}           |  14 +-
 src/models/ratecategory.rs                    |   2 +-
 src/models/server.rs                          |  28 +++
 src/routes/cloud/add.rs                       |  50 +++++
 src/routes/cloud/get.rs                       |  46 +++++
 src/routes/cloud/mod.rs                       |   7 +
 .../{stack/service.rs => cloud/update.rs}     |   0
 src/routes/mod.rs                             |   7 +-
 src/routes/project/add.rs                     |  78 +++++++
 src/routes/{stack => project}/compose.rs      |  34 +--
 src/routes/project/delete.rs                  |  33 +++
 src/routes/project/deploy.rs                  |  79 +++++++
 src/routes/project/get.rs                     |  47 +++++
 src/routes/{stack => project}/mod.rs          |   1 +
 .../stack.rs => routes/project/service.rs}    |   0
 src/routes/project/update.rs                  |  65 ++++++
 src/routes/server/add.rs                      |  50 +++++
 src/routes/server/get.rs                      |  46 +++++
 src/routes/server/mod.rs                      |   5 +
 src/routes/stack/add.rs                       |  78 -------
 src/routes/stack/deploy.rs                    |  58 ------
 src/routes/stack/get.rs                       |  48 -----
 src/routes/stack/update.rs                    |  65 ------
 src/services/mod.rs                           |   2 +-
 src/services/project.rs                       |   0
 src/services/rating.rs                        |   6 +-
 src/startup.rs                                |  36 +++-
 tests/dockerhub.rs                            |  18 +-
 tests/model_user_stack.rs                     |  18 +-
 124 files changed, 1458 insertions(+), 582 deletions(-)
 create mode 100644 migrations/20240228125751_creating_deployments.down.sql
 create mode 100644 migrations/20240228125751_creating_deployments.up.sql
 create mode 100644 migrations/20240229072555_creating_cloud.down.sql
 create mode 100644 migrations/20240229072555_creating_cloud.up.sql
 create mode 100644 migrations/20240229075843_creating_user_stack_cloud_relation.down.sql
 create mode 100644 migrations/20240229075843_creating_user_stack_cloud_relation.up.sql
 create mode 100644 migrations/20240229080559_creating_cloud_server.down.sql
 create mode 100644 migrations/20240229080559_creating_cloud_server.up.sql
 create mode 100644 migrations/20240229083517_creating_user_stack_server_relation.down.sql
 create mode 100644 migrations/20240229083517_creating_user_stack_server_relation.up.sql
 create mode 100644 src/db/cloud.rs
 create mode 100644 src/db/deployment.rs
 create mode 100644 src/db/project.rs
 create mode 100644 src/db/server.rs
 delete mode 100644 src/db/stack.rs
 create mode 100644 src/forms/cloud.rs
 rename src/forms/{stack => project}/app.rs (87%)
 rename src/forms/{stack => project}/compose_networks.rs (94%)
 rename src/forms/{stack => project}/custom.rs (90%)
 rename src/forms/{stack => project}/docker_image.rs (100%)
 rename src/forms/{stack => project}/domain_list.rs (100%)
 rename src/forms/{stack => project}/environment.rs (100%)
 rename src/forms/{stack => project}/feature.rs (93%)
 rename src/forms/{stack => project}/form.rs (63%)
 rename src/forms/{stack => project}/icon.rs (85%)
 rename src/forms/{stack => project}/icon_dark.rs (100%)
 rename src/forms/{stack => project}/icon_light.rs (100%)
 rename src/forms/{stack => project}/mod.rs (100%)
 rename src/forms/{stack => project}/network.rs (96%)
 rename src/forms/{stack => project}/network_driver.rs (100%)
 rename src/forms/{stack => project}/payload.rs (55%)
 rename src/forms/{stack => project}/port.rs (100%)
 rename src/forms/{stack => project}/price.rs (100%)
 rename src/forms/{stack => project}/requirements.rs (100%)
 rename src/forms/{stack => project}/role.rs (100%)
 rename src/forms/{stack => project}/service.rs (93%)
 rename src/forms/{stack => project}/service_networks.rs (98%)
 rename src/forms/{stack => project}/var.rs (100%)
 rename src/forms/{stack => project}/version.rs (100%)
 rename src/forms/{stack => project}/volume.rs (95%)
 rename src/forms/{stack => project}/volumes.rs (83%)
 rename src/forms/{stack => project}/web.rs (88%)
 create mode 100644 src/forms/server.rs
 rename src/helpers/{stack => project}/builder.rs (81%)
 rename src/helpers/{stack => project}/builder_config.rs (100%)
 rename src/helpers/{stack => project}/dctypes/advanced_build_step.rs (95%)
 rename src/helpers/{stack => project}/dctypes/advanced_network_settings.rs (100%)
 rename src/helpers/{stack => project}/dctypes/advanced_networks.rs (92%)
 rename src/helpers/{stack => project}/dctypes/build_args.rs (100%)
 rename src/helpers/{stack => project}/dctypes/build_step.rs (84%)
 rename src/helpers/{stack => project}/dctypes/compose.rs (96%)
 rename src/helpers/{stack => project}/dctypes/compose_file.rs (92%)
 rename src/helpers/{stack => project}/dctypes/compose_network.rs (83%)
 rename src/helpers/{stack => project}/dctypes/compose_network_setting_details.rs (100%)
 rename src/helpers/{stack => project}/dctypes/compose_networks.rs (93%)
 rename src/helpers/{stack => project}/dctypes/compose_volume.rs (79%)
 rename src/helpers/{stack => project}/dctypes/depends_condition.rs (100%)
 rename src/helpers/{stack => project}/dctypes/depends_on_options.rs (95%)
 rename src/helpers/{stack => project}/dctypes/env_file.rs (100%)
 rename src/helpers/{stack => project}/dctypes/environment.rs (94%)
 rename src/helpers/{stack => project}/dctypes/extension.rs (94%)
 rename src/helpers/{stack => project}/dctypes/extension_parse_error.rs (100%)
 rename src/helpers/{stack => project}/dctypes/external_network_setting_bool.rs (100%)
 rename src/helpers/{stack => project}/dctypes/external_volume.rs (100%)
 rename src/helpers/{stack => project}/dctypes/ipam.rs (89%)
 rename src/helpers/{stack => project}/dctypes/ipam_config.rs (100%)
 rename src/helpers/{stack => project}/dctypes/labels.rs (100%)
 rename src/helpers/{stack => project}/dctypes/logging_parameters.rs (93%)
 rename src/helpers/{stack => project}/dctypes/mod.rs (99%)
 rename src/helpers/{stack => project}/dctypes/network_settings.rs (96%)
 rename src/helpers/{stack => project}/dctypes/networks.rs (92%)
 rename src/helpers/{stack => project}/dctypes/port.rs (94%)
 rename src/helpers/{stack => project}/dctypes/ports.rs (92%)
 rename src/helpers/{stack => project}/dctypes/published_port.rs (100%)
 rename src/helpers/{stack => project}/dctypes/service.rs (99%)
 rename src/helpers/{stack => project}/dctypes/services.rs (93%)
 rename src/helpers/{stack => project}/dctypes/single_service.rs (81%)
 rename src/helpers/{stack => project}/dctypes/sys_ctls.rs (94%)
 rename src/helpers/{stack => project}/dctypes/tmpfs.rs (100%)
 rename src/helpers/{stack => project}/dctypes/top_level_volumes.rs (93%)
 rename src/helpers/{stack => project}/dctypes/ulimit.rs (100%)
 rename src/helpers/{stack => project}/dctypes/ulimits.rs (93%)
 rename src/helpers/{stack => project}/mod.rs (100%)
 create mode 100644 src/models/cloud.rs
 create mode 100644 src/models/deployment.rs
 rename src/models/{stack.rs => project.rs} (79%)
 create mode 100644 src/models/server.rs
 create mode 100644 src/routes/cloud/add.rs
 create mode 100644 src/routes/cloud/get.rs
 create mode 100644 src/routes/cloud/mod.rs
 rename src/routes/{stack/service.rs => cloud/update.rs} (100%)
 create mode 100644 src/routes/project/add.rs
 rename src/routes/{stack => project}/compose.rs (50%)
 create mode 100644 src/routes/project/delete.rs
 create mode 100644 src/routes/project/deploy.rs
 create mode 100644 src/routes/project/get.rs
 rename src/routes/{stack => project}/mod.rs (92%)
 rename src/{services/stack.rs => routes/project/service.rs} (100%)
 create mode 100644 src/routes/project/update.rs
 create mode 100644 src/routes/server/add.rs
 create mode 100644 src/routes/server/get.rs
 create mode 100644 src/routes/server/mod.rs
 delete mode 100644 src/routes/stack/add.rs
 delete mode 100644 src/routes/stack/deploy.rs
 delete mode 100644 src/routes/stack/get.rs
 delete mode 100644 src/routes/stack/update.rs
 create mode 100644 src/services/project.rs

diff --git a/README.md b/README.md
index 53879ed3..09f466ff 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
 
 
 Stacker - is an application that helps users to create custom IT solutions based on dockerized open 
-source apps and user's custom applications docker containers. Users can build their own stack of applications, and 
+source apps and user's custom applications docker containers. Users can build their own project of applications, and 
 deploy the final result to their favorite clouds using TryDirect API.
 
 Application development will include:
@@ -36,9 +36,9 @@ Stacker (API) - Serves API clients
 Authentication made through TryDirect OAuth, here we have only client 
 Database (Read only)
 Logging/Tracing (Files) / Quickwit for future 
-/stack (WebUI, as a result we have a JSON)
-/stack/deploy -> sends deploy command to TryDirect Install service 
-/stack/deploy/status - get installation progress (rabbitmq client),
+/project (WebUI, as a result we have a JSON)
+/project/deploy -> sends deploy command to TryDirect Install service 
+/project/deploy/status - get installation progress (rabbitmq client),
 
 #### TODO 
 Find out how to get user's token for queue
@@ -76,7 +76,7 @@ sqlx migrate revert
 
 #### Deploy 
 ```
-curl -X POST -H "Content-Type: application/json" -d @custom-stack-payload-2.json http://127.0.0.1:8000/stack    
+curl -X POST -H "Content-Type: application/json" -d @custom-stack-payload-2.json http://127.0.0.1:8000/project    
 ```
 
 
diff --git a/migrations/20230903063840_creating_rating_tables.up.sql b/migrations/20230903063840_creating_rating_tables.up.sql
index 28422706..156c7221 100644
--- a/migrations/20230903063840_creating_rating_tables.up.sql
+++ b/migrations/20230903063840_creating_rating_tables.up.sql
@@ -3,7 +3,7 @@
 CREATE TYPE rate_category AS ENUM (
     'application',
     'cloud',      
-    'stack',
+    'project',
     'deploymentSpeed',
     'documentation',
     'design',
diff --git a/migrations/20230905145525_creating_stack_tables.down.sql b/migrations/20230905145525_creating_stack_tables.down.sql
index 2f2a6e48..7f367dff 100644
--- a/migrations/20230905145525_creating_stack_tables.down.sql
+++ b/migrations/20230905145525_creating_stack_tables.down.sql
@@ -1,2 +1,2 @@
 -- Add down migration script here
-DROP TABLE user_stack;
+DROP TABLE project;
diff --git a/migrations/20230905145525_creating_stack_tables.up.sql b/migrations/20230905145525_creating_stack_tables.up.sql
index b20b2cdb..333b27f4 100644
--- a/migrations/20230905145525_creating_stack_tables.up.sql
+++ b/migrations/20230905145525_creating_stack_tables.up.sql
@@ -1,4 +1,4 @@
-CREATE TABLE user_stack (
+CREATE TABLE project (
     id serial4 NOT NULL,
     stack_id uuid NOT NULL,
     user_id VARCHAR(50) NOT NULL,
@@ -6,8 +6,9 @@ CREATE TABLE user_stack (
     body JSON NOT NULL,
     created_at timestamptz NOT NULL,
     updated_at timestamptz NOT NULL,
-    CONSTRAINT user_stack_pkey PRIMARY KEY (id)
+    CONSTRAINT project_pkey PRIMARY KEY (id)
 );
 
-CREATE INDEX idx_stack_id ON user_stack(stack_id);
-CREATE INDEX idx_stack_user_id ON user_stack(user_id);
\ No newline at end of file
+CREATE INDEX idx_project_stack_id ON project(stack_id);
+CREATE INDEX idx_project_user_id ON project(user_id);
+CREATE INDEX idx_project_name ON project(name);
diff --git a/migrations/20240228125751_creating_deployments.down.sql b/migrations/20240228125751_creating_deployments.down.sql
new file mode 100644
index 00000000..228cc137
--- /dev/null
+++ b/migrations/20240228125751_creating_deployments.down.sql
@@ -0,0 +1,2 @@
+-- Add up migration script here
+DROP table deployment;
\ No newline at end of file
diff --git a/migrations/20240228125751_creating_deployments.up.sql b/migrations/20240228125751_creating_deployments.up.sql
new file mode 100644
index 00000000..7a06d3b8
--- /dev/null
+++ b/migrations/20240228125751_creating_deployments.up.sql
@@ -0,0 +1,14 @@
+-- Add up migration script here
+CREATE TABLE deployment (
+        id serial4 NOT NULL,
+        project_id integer NOT NULL,
+        body JSON NOT NULL,
+        deleted BOOLEAN DEFAULT FALSE,
+        status VARCHAR(32) NOT NULL,
+        created_at timestamptz NOT NULL,
+        updated_at timestamptz NOT NULL,
+        CONSTRAINT fk_project FOREIGN KEY(project_id) REFERENCES project(id),
+        CONSTRAINT deployment_pkey PRIMARY KEY (id)
+);
+
+CREATE INDEX idx_deployment_project_id ON deployment(project_id);
diff --git a/migrations/20240229072555_creating_cloud.down.sql b/migrations/20240229072555_creating_cloud.down.sql
new file mode 100644
index 00000000..2a04e928
--- /dev/null
+++ b/migrations/20240229072555_creating_cloud.down.sql
@@ -0,0 +1,2 @@
+-- Add down migration script here
+DROP table cloud;
diff --git a/migrations/20240229072555_creating_cloud.up.sql b/migrations/20240229072555_creating_cloud.up.sql
new file mode 100644
index 00000000..c842d3f2
--- /dev/null
+++ b/migrations/20240229072555_creating_cloud.up.sql
@@ -0,0 +1,14 @@
+CREATE TABLE cloud (
+        id serial4 NOT NULL,
+        user_id VARCHAR(50) NOT NULL,
+        provider VARCHAR(50) NOT NULL,
+        cloud_token VARCHAR(255) ,
+        cloud_key VARCHAR(255),
+        cloud_secret VARCHAR(255),
+        save_token BOOLEAN DEFAULT FALSE,
+        created_at timestamptz NOT NULL,
+        updated_at timestamptz NOT NULL,
+        CONSTRAINT user_cloud_pkey PRIMARY KEY (id)
+);
+
+CREATE INDEX idx_deployment_user_cloud_user_id ON cloud(user_id);
\ No newline at end of file
diff --git a/migrations/20240229075843_creating_user_stack_cloud_relation.down.sql b/migrations/20240229075843_creating_user_stack_cloud_relation.down.sql
new file mode 100644
index 00000000..02d2fe54
--- /dev/null
+++ b/migrations/20240229075843_creating_user_stack_cloud_relation.down.sql
@@ -0,0 +1,2 @@
+-- Add down migration script here
+ALTER table project DROP COLUMN cloud_id;
\ No newline at end of file
diff --git a/migrations/20240229075843_creating_user_stack_cloud_relation.up.sql b/migrations/20240229075843_creating_user_stack_cloud_relation.up.sql
new file mode 100644
index 00000000..5f65c667
--- /dev/null
+++ b/migrations/20240229075843_creating_user_stack_cloud_relation.up.sql
@@ -0,0 +1,3 @@
+-- Add up migration script here
+ALTER table project ADD COLUMN cloud_id INT CONSTRAINT project_cloud_id REFERENCES cloud(id) ON UPDATE CASCADE ON DELETE CASCADE;
+
diff --git a/migrations/20240229080559_creating_cloud_server.down.sql b/migrations/20240229080559_creating_cloud_server.down.sql
new file mode 100644
index 00000000..f0fa9822
--- /dev/null
+++ b/migrations/20240229080559_creating_cloud_server.down.sql
@@ -0,0 +1,3 @@
+DROP INDEX idx_server_user_id;
+DROP INDEX idx_server_cloud_id;
+DROP table server;
diff --git a/migrations/20240229080559_creating_cloud_server.up.sql b/migrations/20240229080559_creating_cloud_server.up.sql
new file mode 100644
index 00000000..091fb8f3
--- /dev/null
+++ b/migrations/20240229080559_creating_cloud_server.up.sql
@@ -0,0 +1,22 @@
+-- Add up migration script here
+
+CREATE TABLE server (
+       id serial4 NOT NULL,
+       user_id VARCHAR(50) NOT NULL,
+       cloud_id integer NOT NULL,
+       project_id integer NOT NULL,
+       region VARCHAR(50) NOT NULL,
+       zone VARCHAR(50),
+       server VARCHAR(255) NOT NULL,
+       os VARCHAR(100) NOT NULL,
+       disk_type VARCHAR(100),
+       created_at timestamptz NOT NULL,
+       updated_at timestamptz NOT NULL,
+       CONSTRAINT user_server_pkey PRIMARY KEY (id),
+       CONSTRAINT fk_server FOREIGN KEY(cloud_id) REFERENCES cloud(id),
+       CONSTRAINT fk_server_project FOREIGN KEY(project_id) REFERENCES project(id)
+);
+
+CREATE INDEX idx_server_user_id ON server(user_id);
+CREATE INDEX idx_server_cloud_id ON server(cloud_id);
+CREATE INDEX idx_server_project_id ON server(project_id);
diff --git a/migrations/20240229083517_creating_user_stack_server_relation.down.sql b/migrations/20240229083517_creating_user_stack_server_relation.down.sql
new file mode 100644
index 00000000..c410fff5
--- /dev/null
+++ b/migrations/20240229083517_creating_user_stack_server_relation.down.sql
@@ -0,0 +1,2 @@
+-- Add down migration script here
+ALTER table server DROP COLUMN project_id;
diff --git a/migrations/20240229083517_creating_user_stack_server_relation.up.sql b/migrations/20240229083517_creating_user_stack_server_relation.up.sql
new file mode 100644
index 00000000..ab021b17
--- /dev/null
+++ b/migrations/20240229083517_creating_user_stack_server_relation.up.sql
@@ -0,0 +1,2 @@
+-- Add up migration script here
+ALTER table server ADD COLUMN project_id integer CONSTRAINT project_id REFERENCES project(id) ON UPDATE CASCADE ON DELETE CASCADE;
diff --git a/src/db/cloud.rs b/src/db/cloud.rs
new file mode 100644
index 00000000..03854342
--- /dev/null
+++ b/src/db/cloud.rs
@@ -0,0 +1,120 @@
+use crate::models;
+use sqlx::PgPool;
+use tracing::Instrument;
+
+pub async fn fetch(pool: &PgPool, id: i32) -> Result<Option<models::Cloud>, String> {
+    tracing::info!("Fetch cloud {}", id);
+    sqlx::query_as!(
+        models::Cloud,
+        r#"SELECT * FROM cloud WHERE id=$1 LIMIT 1 "#, id
+    )
+    .fetch_one(pool)
+    .await
+    .map(|cloud| Some(cloud))
+    .or_else(|err| match err {
+        sqlx::Error::RowNotFound => Ok(None),
+        e => {
+            tracing::error!("Failed to fetch cloud, error: {:?}", e);
+            Err("Could not fetch data".to_string())
+        }
+    })
+}
+
+pub async fn fetch_by_user(pool: &PgPool, user_id: &str) -> Result<Vec<models::Cloud>, String> {
+    let query_span = tracing::info_span!("Fetch clouds by user id.");
+    sqlx::query_as!(
+        models::Cloud,
+        r#"
+        SELECT
+            *
+        FROM cloud
+        WHERE user_id=$1
+        "#,
+        user_id
+    )
+        .fetch_all(pool)
+        .instrument(query_span)
+        .await
+        .map_err(|err| {
+            tracing::error!("Failed to fetch cloud, error: {:?}", err);
+            "".to_string()
+        })
+}
+
+
+pub async fn insert(pool: &PgPool, mut cloud: models::Cloud) -> Result<models::Cloud, String> {
+    let query_span = tracing::info_span!("Saving user's cloud data into the database");
+    sqlx::query!(
+        r#"
+        INSERT INTO cloud (
+        user_id,
+        provider,
+        cloud_token,
+        cloud_key,
+        cloud_secret,
+        save_token,
+        created_at,
+        updated_at)
+        VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
+        RETURNING id;
+        "#,
+        cloud.user_id,
+        cloud.provider,
+        cloud.cloud_token,
+        cloud.cloud_key,
+        cloud.cloud_secret,
+        cloud.save_token,
+        cloud.created_at,
+        cloud.updated_at,
+    )
+    .fetch_one(pool)
+    .instrument(query_span)
+    .await
+    .map(move |result| {
+        cloud.id = result.id;
+        cloud
+    })
+    .map_err(|e| {
+        tracing::error!("Failed to execute query: {:?}", e);
+        "Failed to insert".to_string()
+    })
+}
+
+pub async fn update(pool: &PgPool, mut cloud: models::Cloud) -> Result<models::Cloud, String> {
+    let query_span = tracing::info_span!("Updating user cloud");
+    sqlx::query_as!(
+        models::Cloud,
+        r#"
+        UPDATE cloud
+        SET
+            user_id=$2,
+            provider=$3,
+            cloud_token=$4,
+            cloud_key=$5,
+            cloud_secret=$6,
+            created_at=$7,
+            updated_at=NOW() at time zone 'utc'
+        WHERE id = $1
+        RETURNING *
+        "#,
+        cloud.id,
+        cloud.user_id,
+        cloud.provider,
+        cloud.cloud_token,
+        cloud.cloud_key,
+        cloud.cloud_secret,
+        cloud.created_at,
+    )
+        .fetch_one(pool)
+        .instrument(query_span)
+        .await
+        .map(|result|{
+            tracing::info!("Cloud info {} have been saved", cloud.id);
+            cloud.updated_at = result.updated_at;
+            cloud
+        })
+        .map_err(|err| {
+            tracing::error!("Failed to execute query: {:?}", err);
+            "".to_string()
+        })
+}
diff --git a/src/db/deployment.rs b/src/db/deployment.rs
new file mode 100644
index 00000000..1a22f02d
--- /dev/null
+++ b/src/db/deployment.rs
@@ -0,0 +1,68 @@
+use crate::models;
+use sqlx::PgPool;
+use tracing::Instrument;
+
+pub async fn insert(pool: &PgPool, mut deployment: models::Deployment) -> Result<models::Deployment, String> {
+    let query_span = tracing::info_span!("Saving new deployment into the database");
+    sqlx::query!(
+        r#"
+        INSERT INTO deployment (project_id, deleted, status, body, created_at, updated_at)
+        VALUES ($1, $2, $3, $4, $5, $6)
+        RETURNING id;
+        "#,
+        deployment.project_id,
+        deployment.deleted,
+        deployment.status,
+        deployment.body,
+        deployment.created_at,
+        deployment.updated_at,
+    )
+        .fetch_one(pool)
+        .instrument(query_span)
+        .await
+        .map(move |result| {
+            deployment.id = result.id;
+            deployment
+        })
+        .map_err(|e| {
+            tracing::error!("Failed to execute query: {:?}", e);
+            "Failed to insert".to_string()
+        })
+}
+
+// pub async fn update(pool: &PgPool, mut deployment: models::Deployment) -> Result<models::Deployment, String> {
+//     let query_span = tracing::info_span!("Updating user deployment into the database");
+//     sqlx::query_as!(
+//         models::Deployment,
+//         r#"
+//         UPDATE deployment
+//         SET
+//             project_id=$2,
+//             deleted=$3,
+//             status=$4,
+//             body=$5,
+//             created_at=$6,
+//             updated_at=NOW() at time zone 'utc'
+//         WHERE id = $1
+//         RETURNING *
+//         "#,
+//         deployment.id,
+//         deployment.project_id,
+//         deployment.deleted,
+//         deployment.status,
+//         deployment.body,
+//         deployment.created_at,
+//     )
+//         .fetch_one(pool)
+//         .instrument(query_span)
+//         .await
+//         .map(|result|{
+//             tracing::info!("Deployment {} have been saved to database", deployment.id);
+//             deployment.updated_at = result.updated_at;
+//             deployment
+//         })
+//         .map_err(|err| {
+//             tracing::error!("Failed to execute query: {:?}", err);
+//             "".to_string()
+//         })
+// }
diff --git a/src/db/mod.rs b/src/db/mod.rs
index a4fe3ae7..35853276 100644
--- a/src/db/mod.rs
+++ b/src/db/mod.rs
@@ -1,4 +1,7 @@
 pub mod client;
 pub mod product;
 pub mod rating;
-pub mod stack;
+pub mod project;
+pub(crate) mod deployment;
+pub(crate) mod cloud;
+pub(crate) mod server;
diff --git a/src/db/project.rs b/src/db/project.rs
new file mode 100644
index 00000000..99a5d920
--- /dev/null
+++ b/src/db/project.rs
@@ -0,0 +1,194 @@
+use crate::models;
+use sqlx::PgPool;
+use sqlx::postgres::PgRow;
+use tracing::Instrument;
+
+pub async fn fetch(pool: &PgPool, id: i32) -> Result<Option<models::Project>, String> {
+    tracing::info!("Fetch project {}", id);
+    sqlx::query_as!(
+        models::Project,
+        r#"
+        SELECT
+            *
+        FROM project
+        WHERE id=$1
+        LIMIT 1
+        "#,
+        id
+    )
+    .fetch_one(pool)
+    .await
+    .map(|project| Some(project))
+    .or_else(|err| match err {
+        sqlx::Error::RowNotFound => Ok(None),
+        e => {
+            tracing::error!("Failed to fetch project, error: {:?}", e);
+            Err("Could not fetch data".to_string())
+        }
+    })
+}
+
+pub async fn fetch_by_user(pool: &PgPool, user_id: &str) -> Result<Vec<models::Project>, String> {
+    let query_span = tracing::info_span!("Fetch projects by user id.");
+    sqlx::query_as!(
+        models::Project,
+        r#"
+        SELECT
+            *
+        FROM project
+        WHERE user_id=$1
+        "#,
+        user_id
+    )
+    .fetch_all(pool)
+    .instrument(query_span)
+    .await
+    .map_err(|err| {
+        tracing::error!("Failed to fetch project, error: {:?}", err);
+        "".to_string()
+    })
+}
+
+pub async fn fetch_one_by_name(pool: &PgPool, name: &str) -> Result<Option<models::Project>, String> {
+    let query_span = tracing::info_span!("Fetch one project by name.");
+    sqlx::query_as!(
+        models::Project,
+        r#"
+        SELECT
+            *
+        FROM project
+        WHERE name=$1
+        LIMIT 1
+        "#,
+        name
+    )
+    .fetch_one(pool)
+    .instrument(query_span)
+    .await
+    .map(|project| Some(project))
+    .or_else(|err| match err {
+        sqlx::Error::RowNotFound => Ok(None),
+        err => {
+            tracing::error!("Failed to fetch one project by name, error: {:?}", err);
+            Err("".to_string())
+        }
+    })
+}
+
+pub async fn insert(pool: &PgPool, mut project: models::Project) -> Result<models::Project, String> {
+    let query_span = tracing::info_span!("Saving new project into the database");
+    sqlx::query!(
+        r#"
+        INSERT INTO project (stack_id, user_id, name, body, created_at, updated_at, cloud_id)
+        VALUES ($1, $2, $3, $4, $5, $6, $7)
+        RETURNING id;
+        "#,
+        project.stack_id,
+        project.user_id,
+        project.name,
+        project.body,
+        project.created_at,
+        project.updated_at,
+        project.cloud_id,
+    )
+    .fetch_one(pool)
+    .instrument(query_span)
+    .await
+    .map(move |result| {
+        project.id = result.id;
+        project
+    })
+    .map_err(|e| {
+        tracing::error!("Failed to execute query: {:?}", e);
+        "Failed to insert".to_string()
+    })
+}
+
+pub async fn update(pool: &PgPool, mut project: models::Project) -> Result<models::Project, String> {
+    let query_span = tracing::info_span!("Updating project");
+    sqlx::query_as!(
+        models::Project,
+        r#"
+        UPDATE project
+        SET 
+            stack_id=$2,
+            user_id=$3,
+            cloud_id=$4,
+            name=$5,
+            body=$6,
+            created_at=$7,
+            updated_at=NOW() at time zone 'utc'
+        WHERE id = $1
+        RETURNING *
+        "#,
+        project.id,
+        project.stack_id,
+        project.user_id,
+        project.cloud_id,
+        project.name,
+        project.body,
+        project.created_at,
+    )
+    .fetch_one(pool)
+    .instrument(query_span)
+    .await
+    .map(|result|{
+        tracing::info!("Project {} have been saved to database", project.id);
+        project.updated_at = result.updated_at;
+        project
+    })
+    .map_err(|err| {
+        tracing::error!("Failed to execute query: {:?}", err);
+        "".to_string()
+    })
+}
+
+pub async fn delete(pool: &PgPool, id: i32) -> Result<Option<PgRow>, String> {
+    tracing::info!("Delete project {}", id);
+    let mut tx = pool.begin().await
+    .map(|result| result)
+    .map_err(|err| {
+        tracing::error!("Failed to execute query: {:?}", err);
+        "".to_string()
+    }).unwrap();
+
+    // delete records from deployment
+    // delete records from server
+    sqlx::query("DELETE FROM deployment where project_id=?")
+        .bind(id)
+        .execute(&mut *tx)
+        .await
+        .map(|result| result)
+        .map_err(|err| {
+            tracing::error!("Failed to execute query: {:?}", err);
+            "".to_string()
+        }).unwrap();
+
+    sqlx::query("DELETE FROM server where project_id=?")
+        .bind(id)
+        .execute(&mut *tx)
+        .await
+        .map(|result| result)
+        .map_err(|err| {
+            tracing::error!("Failed to execute query: {:?}", err);
+            "".to_string()
+        }).unwrap();
+
+    sqlx::query("DELETE FROM project where id=?")
+        .bind(id)
+        .execute(&mut *tx)
+        .await
+        .map(|result| result)
+        .map_err(|err| {
+            tracing::error!("Failed to execute query: {:?}", err);
+            "".to_string()
+        }).unwrap();
+
+    tx.commit().await;
+
+    // tracing::error!("Failed to delete project, error: {:?}", e);
+    // Err("Could not fetch data".to_string())
+
+    Ok(None)
+}
+
diff --git a/src/db/server.rs b/src/db/server.rs
new file mode 100644
index 00000000..c471cf7e
--- /dev/null
+++ b/src/db/server.rs
@@ -0,0 +1,152 @@
+use crate::models;
+use sqlx::PgPool;
+use tracing::Instrument;
+
+pub async fn fetch(pool: &PgPool, id: i32) -> Result<Option<models::Server>, String> {
+    tracing::info!("Fetch server {}", id);
+    sqlx::query_as!(
+        models::Server,
+        r#"SELECT * FROM server WHERE id=$1 LIMIT 1 "#, id
+    )
+        .fetch_one(pool)
+        .await
+        .map(|server| Some(server))
+        .or_else(|err| match err {
+            sqlx::Error::RowNotFound => Ok(None),
+            e => {
+                tracing::error!("Failed to fetch server, error: {:?}", e);
+                Err("Could not fetch data".to_string())
+            }
+        })
+}
+
+pub async fn fetch_by_user(pool: &PgPool, user_id: &str) -> Result<Vec<models::Server>, String> {
+    let query_span = tracing::info_span!("Fetch servers by user id.");
+    sqlx::query_as!(
+        models::Server,
+        r#"
+        SELECT
+            *
+        FROM server
+        WHERE user_id=$1
+        "#,
+        user_id
+    )
+        .fetch_all(pool)
+        .instrument(query_span)
+        .await
+        .map_err(|err| {
+            tracing::error!("Failed to fetch server, error: {:?}", err);
+            "".to_string()
+        })
+}
+
+
+pub async fn fetch_by_project(pool: &PgPool, project_id: i32) -> Result<Vec<models::Server>, String> {
+    let query_span = tracing::info_span!("Fetch servers by project/project id.");
+    sqlx::query_as!(
+        models::Server,
+        r#"
+        SELECT
+            *
+        FROM server
+        WHERE project_id=$1
+        "#,
+        project_id
+    )
+        .fetch_all(pool)
+        .instrument(query_span)
+        .await
+        .map_err(|err| {
+            tracing::error!("Failed to fetch servers, error: {:?}", err);
+            "".to_string()
+        })
+}
+
+
+pub async fn insert(pool: &PgPool, mut server: models::Server) -> Result<models::Server, String> {
+    let query_span = tracing::info_span!("Saving user's server data into the database");
+    sqlx::query!(
+        r#"
+        INSERT INTO server (
+        user_id,
+        cloud_id,
+        project_id,
+        region,
+        zone,
+        server,
+        os,
+        disk_type,
+        created_at,
+        updated_at)
+        VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
+        RETURNING id;
+        "#,
+        server.user_id,
+        server.cloud_id,
+        server.project_id,
+        server.region,
+        server.zone,
+        server.server,
+        server.os,
+        server.disk_type,
+        server.created_at,
+        server.updated_at,
+    )
+        .fetch_one(pool)
+        .instrument(query_span)
+        .await
+        .map(move |result| {
+            server.id = result.id;
+            server
+        })
+        .map_err(|e| {
+            tracing::error!("Failed to execute query: {:?}", e);
+            "Failed to insert".to_string()
+        })
+}
+
+pub async fn update(pool: &PgPool, mut server: models::Server) -> Result<models::Server, String> {
+    let query_span = tracing::info_span!("Updating user server");
+    sqlx::query_as!(
+        models::Server,
+        r#"
+        UPDATE server
+        SET
+            user_id=$2,
+            cloud_id=$3,
+            project_id=$4,
+            region=$5,
+            zone=$6,
+            server=$7,
+            os=$8,
+            disk_type=$9,
+            created_at=$10,
+            updated_at=NOW() at time zone 'utc'
+        WHERE id = $1
+        RETURNING *
+        "#,
+        server.id,
+        server.user_id,
+        server.cloud_id,
+        server.project_id,
+        server.region,
+        server.zone,
+        server.server,
+        server.os,
+        server.disk_type,
+        server.created_at,
+    )
+        .fetch_one(pool)
+        .instrument(query_span)
+        .await
+        .map(|result|{
+            tracing::info!("Server info {} have been saved", server.id);
+            server.updated_at = result.updated_at;
+            server
+        })
+        .map_err(|err| {
+            tracing::error!("Failed to execute query: {:?}", err);
+            "".to_string()
+        })
+}
diff --git a/src/db/stack.rs b/src/db/stack.rs
deleted file mode 100644
index def7a367..00000000
--- a/src/db/stack.rs
+++ /dev/null
@@ -1,140 +0,0 @@
-use crate::models;
-use sqlx::PgPool;
-use tracing::Instrument;
-
-pub async fn fetch(pool: &PgPool, id: i32) -> Result<Option<models::Stack>, String> {
-    tracing::info!("Fecth stack {}", id);
-    sqlx::query_as!(
-        models::Stack,
-        r#"
-        SELECT
-            *
-        FROM user_stack
-        WHERE id=$1
-        LIMIT 1
-        "#,
-        id
-    )
-    .fetch_one(pool)
-    .await
-    .map(|stack| Some(stack))
-    .or_else(|err| match err {
-        sqlx::Error::RowNotFound => Ok(None),
-        e => {
-            tracing::error!("Failed to fetch stack, error: {:?}", e);
-            Err("Could not fetch data".to_string())
-        }
-    })
-}
-
-pub async fn fetch_by_user(pool: &PgPool, user_id: &str) -> Result<Vec<models::Stack>, String> {
-    let query_span = tracing::info_span!("Fetch stacks by user id.");
-    sqlx::query_as!(
-        models::Stack,
-        r#"
-        SELECT
-            *
-        FROM user_stack
-        WHERE user_id=$1
-        "#,
-        user_id
-    )
-    .fetch_all(pool)
-    .instrument(query_span)
-    .await
-    .map_err(|err| {
-        tracing::error!("Failed to fetch stack, error: {:?}", err);
-        "".to_string()
-    })
-}
-
-pub async fn fetch_one_by_name(pool: &PgPool, name: &str) -> Result<Option<models::Stack>, String> {
-    let query_span = tracing::info_span!("Fetch one stack by name.");
-    sqlx::query_as!(
-        models::Stack,
-        r#"
-        SELECT
-            *
-        FROM user_stack
-        WHERE name=$1
-        LIMIT 1
-        "#,
-        name
-    )
-    .fetch_one(pool)
-    .instrument(query_span)
-    .await
-    .map(|stack| Some(stack))
-    .or_else(|err| match err {
-        sqlx::Error::RowNotFound => Ok(None),
-        err => {
-            tracing::error!("Failed to fetch one stack by name, error: {:?}", err);
-            Err("".to_string())
-        }
-    })
-}
-
-pub async fn insert(pool: &PgPool, mut stack: models::Stack) -> Result<models::Stack, String> {
-    let query_span = tracing::info_span!("Saving new stack into the database");
-    sqlx::query!(
-        r#"
-        INSERT INTO user_stack (stack_id, user_id, name, body, created_at, updated_at)
-        VALUES ($1, $2, $3, $4, $5, $6)
-        RETURNING id;
-        "#,
-        stack.stack_id,
-        stack.user_id,
-        stack.name,
-        stack.body,
-        stack.created_at,
-        stack.updated_at,
-    )
-    .fetch_one(pool)
-    .instrument(query_span)
-    .await
-    .map(move |result| {
-        stack.id = result.id;
-        stack
-    })
-    .map_err(|e| {
-        tracing::error!("Failed to execute query: {:?}", e);
-        "Failed to insert".to_string()
-    })
-}
-
-pub async fn update(pool: &PgPool, mut stack: models::Stack) -> Result<models::Stack, String> {
-    let query_span = tracing::info_span!("Updating user stack into the database");
-    sqlx::query_as!(
-        models::Stack,
-        r#"
-        UPDATE user_stack
-        SET 
-            stack_id=$2,
-            user_id=$3,
-            name=$4,
-            body=$5,
-            created_at=$6, 
-            updated_at=NOW() at time zone 'utc'
-        WHERE id = $1
-        RETURNING *
-        "#,
-        stack.id,
-        stack.stack_id,
-        stack.user_id,
-        stack.name,
-        stack.body,
-        stack.created_at,
-    )
-    .fetch_one(pool)
-    .instrument(query_span)
-    .await
-    .map(|result|{
-        tracing::info!("Stack {} have been saved to database", stack.id);
-        stack.updated_at = result.updated_at;
-        stack
-    })
-    .map_err(|err| {
-        tracing::error!("Failed to execute query: {:?}", err);
-        "".to_string()
-    })
-}
diff --git a/src/forms/cloud.rs b/src/forms/cloud.rs
new file mode 100644
index 00000000..e742952d
--- /dev/null
+++ b/src/forms/cloud.rs
@@ -0,0 +1,34 @@
+use crate::models;
+use serde::{Deserialize, Serialize};
+use serde_valid::Validate;
+use chrono::{DateTime, Utc};
+
+#[derive(Serialize, Deserialize, Debug, Validate)]
+pub struct Cloud {
+    pub id: i32,
+    pub user_id: String,
+    #[validate(min_length = 2)]
+    #[validate(max_length = 50)]
+    pub provider: String,
+    pub cloud_token: Option<String>,
+    pub cloud_key: Option<String>,
+    pub cloud_secret: Option<String>,
+    pub save_token: Option<bool>,
+    pub created_at: DateTime<Utc>,
+    pub updated_at: DateTime<Utc>,
+}
+
+impl Into<models::Cloud> for Cloud {
+    fn into(self) -> models::Cloud {
+        let mut cloud = models::Cloud::default();
+        cloud.user_id = self.user_id;
+        cloud.provider = self.provider;
+        cloud.cloud_token = Some(String::from(""));
+        cloud.cloud_key = Some(String::from(""));
+        cloud.cloud_secret = Some(String::from(""));
+        cloud.created_at = Utc::now();
+        cloud.updated_at = Utc::now();
+
+        cloud
+    }
+}
diff --git a/src/forms/mod.rs b/src/forms/mod.rs
index 9647ea6e..f1522632 100644
--- a/src/forms/mod.rs
+++ b/src/forms/mod.rs
@@ -1,5 +1,7 @@
 mod rating;
-pub mod stack;
+pub mod project;
 pub mod user;
+pub(crate) mod cloud;
+pub(crate) mod server;
 
 pub use rating::*;
diff --git a/src/forms/stack/app.rs b/src/forms/project/app.rs
similarity index 87%
rename from src/forms/stack/app.rs
rename to src/forms/project/app.rs
index 93529a37..64598e61 100644
--- a/src/forms/stack/app.rs
+++ b/src/forms/project/app.rs
@@ -4,8 +4,8 @@ use indexmap::IndexMap;
 use serde_json::Value;
 use serde::{Deserialize, Serialize};
 use serde_valid::Validate;
-use crate::forms::stack::network::Network;
-use crate::forms::stack::{DockerImage, replace_id_with_name};
+use crate::forms::project::network::Network;
+use crate::forms::project::{DockerImage, replace_id_with_name};
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct App {
@@ -30,15 +30,15 @@ pub struct App {
     #[serde(rename = "type")]
     pub type_field: String,
     #[serde(flatten)]
-    pub role: forms::stack::Role,
+    pub role: forms::project::Role,
     pub default: Option<bool>,
-    pub versions: Option<Vec<forms::stack::Version>>,
+    pub versions: Option<Vec<forms::project::Version>>,
     #[serde(flatten)]
     #[validate]
     pub docker_image: DockerImage,
     #[serde(flatten)]
     #[validate]
-    pub requirements: forms::stack::Requirements,
+    pub requirements: forms::project::Requirements,
     #[validate(minimum = 1)]
     pub popularity: Option<u32>,
     pub commercial: Option<bool>,
@@ -47,8 +47,8 @@ pub struct App {
     pub suggested: Option<bool>,
     pub dependency: Option<Value>,
     pub avoid_render: Option<bool>,
-    pub price: Option<forms::stack::Price>,
-    pub icon: Option<forms::stack::Icon>,
+    pub price: Option<forms::project::Price>,
+    pub icon: Option<forms::project::Icon>,
     pub domain: Option<String>,
     pub category_id: Option<u32>,
     pub parent_app_id: Option<u32>,
@@ -63,13 +63,13 @@ pub struct App {
     pub url_git: Option<String>,
     #[validate(enumerate("always", "no", "unless-stopped", "on-failure"))]
     pub restart: String,
-    pub volumes: Option<Vec<forms::stack::Volume>>,
+    pub volumes: Option<Vec<forms::project::Volume>>,
     #[serde(flatten)]
-    pub environment: forms::stack::Environment,
+    pub environment: forms::project::Environment,
     #[serde(flatten)]
-    pub network: forms::stack::ServiceNetworks,
+    pub network: forms::project::ServiceNetworks,
     #[validate]
-    pub shared_ports: Option<Vec<forms::stack::Port>>,
+    pub shared_ports: Option<Vec<forms::project::Port>>,
 }
 
 impl App {
@@ -151,8 +151,8 @@ impl App {
     }
 }
 
-impl AsRef<forms::stack::DockerImage> for App {
-    fn as_ref(&self) -> &forms::stack::DockerImage {
+impl AsRef<forms::project::DockerImage> for App {
+    fn as_ref(&self) -> &forms::project::DockerImage {
         &self.docker_image
     }
 }
diff --git a/src/forms/stack/compose_networks.rs b/src/forms/project/compose_networks.rs
similarity index 94%
rename from src/forms/stack/compose_networks.rs
rename to src/forms/project/compose_networks.rs
index afbd6f81..bb45a365 100644
--- a/src/forms/stack/compose_networks.rs
+++ b/src/forms/project/compose_networks.rs
@@ -1,8 +1,8 @@
 use serde::{Deserialize, Serialize};
 use docker_compose_types as dctypes;
 use indexmap::IndexMap;
-use crate::forms::stack;
-use crate::forms::stack::network::Network;
+use crate::forms::project;
+use crate::forms::project::network::Network;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct ComposeNetworks {
diff --git a/src/forms/stack/custom.rs b/src/forms/project/custom.rs
similarity index 90%
rename from src/forms/stack/custom.rs
rename to src/forms/project/custom.rs
index 4e6bd871..8be69cc7 100644
--- a/src/forms/stack/custom.rs
+++ b/src/forms/project/custom.rs
@@ -2,17 +2,17 @@ use serde::{Deserialize, Serialize};
 use crate::forms;
 use indexmap::IndexMap;
 use docker_compose_types as dctypes;
-use crate::forms::stack::Network;
+use crate::forms::project::Network;
 use serde_valid::Validate;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Custom {
     #[validate]
-    pub web: Vec<forms::stack::Web>,
+    pub web: Vec<forms::project::Web>,
     #[validate]
-    pub feature: Option<Vec<forms::stack::Feature>>,
+    pub feature: Option<Vec<forms::project::Feature>>,
     #[validate]
-    pub service: Option<Vec<forms::stack::Service>>,
+    pub service: Option<Vec<forms::project::Service>>,
     #[validate(minimum = 0)]
     #[validate(maximum = 10)]
     pub servers_count: u32,
@@ -25,13 +25,13 @@ pub struct Custom {
     pub custom_stack_category: Option<Vec<String>>,
     pub custom_stack_short_description: Option<String>,
     pub custom_stack_description: Option<String>,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 255)]
-    pub project_name: String,
+    // #[validate(min_length = 3)]
+    // #[validate(max_length = 255)]
+    pub project_name: Option<String>,
     pub project_overview: Option<String>,
     pub project_description: Option<String>,
     #[serde(flatten)]
-    pub networks: forms::stack::ComposeNetworks, // all networks
+    pub networks: forms::project::ComposeNetworks, // all networks
 }
 
 
diff --git a/src/forms/stack/docker_image.rs b/src/forms/project/docker_image.rs
similarity index 100%
rename from src/forms/stack/docker_image.rs
rename to src/forms/project/docker_image.rs
diff --git a/src/forms/stack/domain_list.rs b/src/forms/project/domain_list.rs
similarity index 100%
rename from src/forms/stack/domain_list.rs
rename to src/forms/project/domain_list.rs
diff --git a/src/forms/stack/environment.rs b/src/forms/project/environment.rs
similarity index 100%
rename from src/forms/stack/environment.rs
rename to src/forms/project/environment.rs
diff --git a/src/forms/stack/feature.rs b/src/forms/project/feature.rs
similarity index 93%
rename from src/forms/stack/feature.rs
rename to src/forms/project/feature.rs
index b6d0cbcb..d5405725 100644
--- a/src/forms/stack/feature.rs
+++ b/src/forms/project/feature.rs
@@ -1,6 +1,6 @@
 use serde::{Deserialize, Serialize};
 use serde_valid::Validate;
-use crate::forms::stack::*;
+use crate::forms::project::*;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Feature {
diff --git a/src/forms/stack/form.rs b/src/forms/project/form.rs
similarity index 63%
rename from src/forms/stack/form.rs
rename to src/forms/project/form.rs
index 3a9d8b70..2ae56bd0 100644
--- a/src/forms/stack/form.rs
+++ b/src/forms/project/form.rs
@@ -6,61 +6,40 @@ use std::fmt;
 use crate::models;
 use crate::forms;
 
+
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct Stack { 
+pub struct ProjectForm {
     // #[validate(min_length=2)]
     // #[validate(max_length=255)]
     #[serde(rename = "commonDomain")]
     pub common_domain: Option<String>,
-    pub domain_list: Option<forms::stack::DomainList>,
+    pub domain_list: Option<forms::project::DomainList>,
     #[validate(min_length = 2)]
     #[validate(max_length = 255)]
     pub stack_code: Option<String>,
-    #[validate(min_length = 2)]
-    #[validate(max_length = 50)]
-    pub region: String,
-    #[validate(min_length = 2)]
-    #[validate(max_length = 50)]
-    pub zone: Option<String>,
-    #[validate(min_length = 2)]
-    #[validate(max_length = 50)]
-    pub server: String,
-    #[validate(min_length = 2)]
-    #[validate(max_length = 50)]
-    pub os: String,
     #[validate(min_length = 3)]
     #[validate(max_length = 50)]
     pub ssl: String,
-    pub vars: Option<Vec<forms::stack::Var>>,
+    pub vars: Option<Vec<forms::project::Var>>,
     pub integrated_features: Option<Vec<Value>>,
     pub extended_features: Option<Vec<Value>>,
     pub subscriptions: Option<Vec<String>>,
     pub form_app: Option<Vec<String>>,
     #[validate(min_length = 3)]
     #[validate(max_length = 50)]
-    pub disk_type: Option<String>,
-    pub save_token: bool,
-    #[validate(min_length = 10)]
-    #[validate(max_length = 255)]
-    pub cloud_token: String,
-    #[validate(min_length = 2)]
-    #[validate(max_length = 50)]
-    pub provider: String,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 50)]
     pub selected_plan: String,
-    pub custom: forms::stack::Custom,
+    pub custom: forms::project::Custom,
 }
 
-impl TryFrom<&models::Stack> for Stack {
+impl TryFrom<&models::Project> for ProjectForm {
     type Error = String;
 
-    fn try_from(stack: &models::Stack) -> Result<Self, Self::Error> {
-        serde_json::from_value::<Stack>(stack.body.clone()).map_err(|err| format!("{:?}", err))
+    fn try_from(project: &models::Project) -> Result<Self, Self::Error> {
+        serde_json::from_value::<ProjectForm>(project.body.clone()).map_err(|err| format!("{:?}", err))
     }
 }
 
-impl Stack {
+impl ProjectForm {
     pub async fn is_readable_docker_image(&self) -> Result<bool, String> {
         let mut is_active = true;
         for app in &self.custom.web {
diff --git a/src/forms/stack/icon.rs b/src/forms/project/icon.rs
similarity index 85%
rename from src/forms/stack/icon.rs
rename to src/forms/project/icon.rs
index 0bcae010..2f1c83c1 100644
--- a/src/forms/stack/icon.rs
+++ b/src/forms/project/icon.rs
@@ -1,5 +1,5 @@
 use serde::{Deserialize, Serialize};
-use crate::forms::stack::*;
+use crate::forms::project::*;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct Icon {
diff --git a/src/forms/stack/icon_dark.rs b/src/forms/project/icon_dark.rs
similarity index 100%
rename from src/forms/stack/icon_dark.rs
rename to src/forms/project/icon_dark.rs
diff --git a/src/forms/stack/icon_light.rs b/src/forms/project/icon_light.rs
similarity index 100%
rename from src/forms/stack/icon_light.rs
rename to src/forms/project/icon_light.rs
diff --git a/src/forms/stack/mod.rs b/src/forms/project/mod.rs
similarity index 100%
rename from src/forms/stack/mod.rs
rename to src/forms/project/mod.rs
diff --git a/src/forms/stack/network.rs b/src/forms/project/network.rs
similarity index 96%
rename from src/forms/stack/network.rs
rename to src/forms/project/network.rs
index a05fd540..9b848ff9 100644
--- a/src/forms/stack/network.rs
+++ b/src/forms/project/network.rs
@@ -1,9 +1,9 @@
 use serde::{Deserialize, Serialize};
 use serde_valid::Validate;
-use crate::forms::stack;
+use crate::forms::project;
 use docker_compose_types as dctypes;
 use indexmap::IndexMap;
-use crate::forms::stack::NetworkDriver;
+use crate::forms::project::NetworkDriver;
 
 
 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
diff --git a/src/forms/stack/network_driver.rs b/src/forms/project/network_driver.rs
similarity index 100%
rename from src/forms/stack/network_driver.rs
rename to src/forms/project/network_driver.rs
diff --git a/src/forms/stack/payload.rs b/src/forms/project/payload.rs
similarity index 55%
rename from src/forms/stack/payload.rs
rename to src/forms/project/payload.rs
index f89b3c6a..70800fed 100644
--- a/src/forms/stack/payload.rs
+++ b/src/forms/project/payload.rs
@@ -13,13 +13,11 @@ pub struct Payload {
     pub(crate) user_email: Option<String>,
     #[serde(rename = "commonDomain")]
     pub common_domain: String,
-    pub domain_list: Option<forms::stack::DomainList>,
-    pub region: String,
-    pub zone: Option<String>,
-    pub server: String,
-    pub os: String,
+    pub domain_list: Option<forms::project::DomainList>,
+    #[serde(flatten)]
+    pub server: models::Server,
     pub ssl: String,
-    pub vars: Option<Vec<forms::stack::Var>>,
+    pub vars: Option<Vec<forms::project::Var>>,
     #[serde(rename = "integrated_features")]
     pub integrated_features: Option<Vec<Value>>,
     #[serde(rename = "extended_features")]
@@ -27,29 +25,25 @@ pub struct Payload {
     pub subscriptions: Option<Vec<String>>,
     pub form_app: Option<Vec<String>>,
     pub disk_type: Option<String>,
-    pub save_token: bool,
-    pub cloud_token: Option<String>,
-    pub cloud_key: Option<String>,
-    pub cloud_secret: Option<String>,
-    pub provider: String,
+    #[serde(flatten)]
+    pub cloud: models::Cloud,
     pub stack_code: String,
-    #[serde(rename = "selected_plan")]
     pub selected_plan: String,
-    pub custom: forms::stack::Custom,
+    pub custom: forms::project::Custom,
     pub docker_compose: Option<Vec<u8>>,
 }
 
-impl TryFrom<&models::Stack> for Payload {
+impl TryFrom<&models::Project> for Payload {
     type Error = String;
 
-    fn try_from(stack: &models::Stack) -> Result<Self, Self::Error> {
-        let mut stack_data = serde_json::from_value::<Payload>(stack.body.clone()).map_err(|err| {
+    fn try_from(project: &models::Project) -> Result<Self, Self::Error> {
+        let mut project_data = serde_json::from_value::<Payload>(project.body.clone()).map_err(|err| {
             format!("{:?}", err)
         })?;
 
-        stack_data.id = Some(stack.id.clone());
-        stack_data.stack_code = stack_data.custom.custom_stack_code.clone();
+        project_data.id = Some(project.id.clone());
+        project_data.stack_code = project_data.custom.custom_stack_code.clone();
 
-        Ok(stack_data)
+        Ok(project_data)
     }
 }
diff --git a/src/forms/stack/port.rs b/src/forms/project/port.rs
similarity index 100%
rename from src/forms/stack/port.rs
rename to src/forms/project/port.rs
diff --git a/src/forms/stack/price.rs b/src/forms/project/price.rs
similarity index 100%
rename from src/forms/stack/price.rs
rename to src/forms/project/price.rs
diff --git a/src/forms/stack/requirements.rs b/src/forms/project/requirements.rs
similarity index 100%
rename from src/forms/stack/requirements.rs
rename to src/forms/project/requirements.rs
diff --git a/src/forms/stack/role.rs b/src/forms/project/role.rs
similarity index 100%
rename from src/forms/stack/role.rs
rename to src/forms/project/role.rs
diff --git a/src/forms/stack/service.rs b/src/forms/project/service.rs
similarity index 93%
rename from src/forms/stack/service.rs
rename to src/forms/project/service.rs
index 5aea4a8b..706e0be2 100644
--- a/src/forms/stack/service.rs
+++ b/src/forms/project/service.rs
@@ -1,6 +1,6 @@
 use serde::{Deserialize, Serialize};
 use serde_valid::Validate;
-use crate::forms::stack::*;
+use crate::forms::project::*;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Service {
diff --git a/src/forms/stack/service_networks.rs b/src/forms/project/service_networks.rs
similarity index 98%
rename from src/forms/stack/service_networks.rs
rename to src/forms/project/service_networks.rs
index 17b51c53..39f03b08 100644
--- a/src/forms/stack/service_networks.rs
+++ b/src/forms/project/service_networks.rs
@@ -24,7 +24,7 @@ impl TryFrom<&ServiceNetworks> for dctypes::Networks {
 
 // IndexMap
 //
-// impl Into<IndexMap<String, MapOrEmpty<NetworkSettings>>> for stack::ComposeNetworks {
+// impl Into<IndexMap<String, MapOrEmpty<NetworkSettings>>> for project::ComposeNetworks {
 //     fn into(self) -> IndexMap<String, MapOrEmpty<NetworkSettings>> {
 //
 //         // let mut default_networks = vec![Network::default()];
diff --git a/src/forms/stack/var.rs b/src/forms/project/var.rs
similarity index 100%
rename from src/forms/stack/var.rs
rename to src/forms/project/var.rs
diff --git a/src/forms/stack/version.rs b/src/forms/project/version.rs
similarity index 100%
rename from src/forms/stack/version.rs
rename to src/forms/project/version.rs
diff --git a/src/forms/stack/volume.rs b/src/forms/project/volume.rs
similarity index 95%
rename from src/forms/stack/volume.rs
rename to src/forms/project/volume.rs
index ab4d3f84..b4bfaedb 100644
--- a/src/forms/stack/volume.rs
+++ b/src/forms/project/volume.rs
@@ -58,8 +58,8 @@ impl Into<dctypes::ComposeVolume> for &Volume {
         // @todo check if host_path is required argument
         driver_opts.insert(String::from("type"), Some(dctypes::SingleValue::String("none".to_string())));
         driver_opts.insert(String::from("o"), Some(dctypes::SingleValue::String("bind".to_string())));
-        // @todo move to config stack docroot on host
-        let path = format!("/root/stack/{}", &host_path);
+        // @todo move to config project docroot on host
+        let path = format!("/root/project/{}", &host_path);
         driver_opts.insert(String::from("device"), Some(dctypes::SingleValue::String(path)));
 
         dctypes::ComposeVolume {
diff --git a/src/forms/stack/volumes.rs b/src/forms/project/volumes.rs
similarity index 83%
rename from src/forms/stack/volumes.rs
rename to src/forms/project/volumes.rs
index 39ec33ea..27548a72 100644
--- a/src/forms/stack/volumes.rs
+++ b/src/forms/project/volumes.rs
@@ -1,5 +1,5 @@
 use serde::{Deserialize, Serialize};
-use crate::forms::stack::*;
+use crate::forms::project::*;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct Volumes {
diff --git a/src/forms/stack/web.rs b/src/forms/project/web.rs
similarity index 88%
rename from src/forms/stack/web.rs
rename to src/forms/project/web.rs
index 87e70cff..2d80cd51 100644
--- a/src/forms/stack/web.rs
+++ b/src/forms/project/web.rs
@@ -1,6 +1,6 @@
 use serde::{Deserialize, Serialize};
 use serde_valid::Validate;
-use crate::forms::stack::*;
+use crate::forms::project::*;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Web {
diff --git a/src/forms/server.rs b/src/forms/server.rs
new file mode 100644
index 00000000..7b2672b2
--- /dev/null
+++ b/src/forms/server.rs
@@ -0,0 +1,36 @@
+use crate::models;
+use serde::{Deserialize, Serialize};
+use serde_valid::Validate;
+use chrono::{DateTime, Utc};
+
+#[derive(Serialize, Deserialize, Debug, Validate)]
+pub struct Server {
+    pub id: i32,
+    pub user_id: String,
+    pub cloud_id: i32,
+    pub project_id: i32,
+    pub region: String,
+    pub zone: Option<String>,
+    pub server: String,
+    pub os: String,
+    pub disk_type: Option<String>,
+    pub created_at: DateTime<Utc>,
+    pub updated_at: DateTime<Utc>,
+}
+
+impl Into<models::Server> for Server {
+    fn into(self) -> models::Server {
+        let mut server = models::Server::default();
+        server.user_id = self.user_id;
+        server.cloud_id = self.cloud_id;
+        server.project_id = self.project_id;
+        server.region = String::from("");
+        server.zone = Some(String::from(""));
+        server.server = String::from("");
+        server.os = String::from("");
+        server.created_at = Utc::now();
+        server.updated_at = Utc::now();
+
+        server
+    }
+}
diff --git a/src/helpers/dockerhub.rs b/src/helpers/dockerhub.rs
index e3f17207..d5e4ad67 100644
--- a/src/helpers/dockerhub.rs
+++ b/src/helpers/dockerhub.rs
@@ -1,4 +1,4 @@
-use crate::forms::stack::DockerImage;
+use crate::forms::project::DockerImage;
 use reqwest::RequestBuilder;
 use serde_derive::{Deserialize, Serialize};
 use serde_json::Value;
diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs
index 17f75f10..74f975b5 100644
--- a/src/helpers/mod.rs
+++ b/src/helpers/mod.rs
@@ -1,7 +1,7 @@
 pub mod client;
 pub(crate) mod json;
 mod mq_manager;
-pub(crate) mod stack;
+pub mod project;
 
 pub use json::*;
 pub use mq_manager::MqManager;
diff --git a/src/helpers/stack/builder.rs b/src/helpers/project/builder.rs
similarity index 81%
rename from src/helpers/stack/builder.rs
rename to src/helpers/project/builder.rs
index e17d3642..b5b0e42b 100644
--- a/src/helpers/stack/builder.rs
+++ b/src/helpers/project/builder.rs
@@ -2,7 +2,7 @@ use crate::forms;
 use docker_compose_types as dctypes;
 use crate::models;
 use serde_yaml;
-use crate::helpers::stack::*;
+use crate::helpers::project::*;
 use tracing::Value;
 
 
@@ -10,26 +10,26 @@ use tracing::Value;
 #[derive(Clone, Debug)]
 pub struct DcBuilder {
     config: Config,
-    pub(crate) stack: models::Stack,
+    pub(crate) project: models::Project,
 }
 
 
 impl DcBuilder {
-    pub fn new(stack: models::Stack) -> Self {
+    pub fn new(project: models::Project) -> Self {
         DcBuilder {
             config: Config::default(),
-            stack,
+            project,
         }
     }
 
-    #[tracing::instrument(name = "building stack")]
+    #[tracing::instrument(name = "building project")]
     pub fn build(&self) -> Result<String, String> {
         let mut compose_content = dctypes::Compose {
             version: Some("3.8".to_string()),
             ..Default::default()
         };
 
-        let apps = forms::stack::Stack::try_from(&self.stack)?;
+        let apps = forms::project::ProjectForm::try_from(&self.project)?;
         let services = apps.custom.services()?;
         let named_volumes = apps.custom.named_volumes()?;
 
@@ -44,7 +44,7 @@ impl DcBuilder {
         tracing::debug!("services {:?}", &services);
         compose_content.services = dctypes::Services(services);
 
-        let fname = format!("./files/{}.yml", self.stack.stack_id);
+        let fname = format!("./files/{}.yml", self.project.stack_id);
         tracing::debug!("Saving docker compose to file {:?}", fname);
         let target_file = std::path::Path::new(fname.as_str());
         let serialized = serde_yaml::to_string(&compose_content)
diff --git a/src/helpers/stack/builder_config.rs b/src/helpers/project/builder_config.rs
similarity index 100%
rename from src/helpers/stack/builder_config.rs
rename to src/helpers/project/builder_config.rs
diff --git a/src/helpers/stack/dctypes/advanced_build_step.rs b/src/helpers/project/dctypes/advanced_build_step.rs
similarity index 95%
rename from src/helpers/stack/dctypes/advanced_build_step.rs
rename to src/helpers/project/dctypes/advanced_build_step.rs
index c0e6e060..41f2f0c0 100644
--- a/src/helpers/stack/dctypes/advanced_build_step.rs
+++ b/src/helpers/project/dctypes/advanced_build_step.rs
@@ -1,5 +1,5 @@
 use serde::{Deserialize, Serialize};
-use crate::helpers::stack::dctypes::*;
+use crate::helpers::project::dctypes::*;
 
 #[derive(Builder, Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Default)]
 #[serde(deny_unknown_fields)]
diff --git a/src/helpers/stack/dctypes/advanced_network_settings.rs b/src/helpers/project/dctypes/advanced_network_settings.rs
similarity index 100%
rename from src/helpers/stack/dctypes/advanced_network_settings.rs
rename to src/helpers/project/dctypes/advanced_network_settings.rs
diff --git a/src/helpers/stack/dctypes/advanced_networks.rs b/src/helpers/project/dctypes/advanced_networks.rs
similarity index 92%
rename from src/helpers/stack/dctypes/advanced_networks.rs
rename to src/helpers/project/dctypes/advanced_networks.rs
index d26aba1f..2b4f0b0a 100644
--- a/src/helpers/stack/dctypes/advanced_networks.rs
+++ b/src/helpers/project/dctypes/advanced_networks.rs
@@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
 use indexmap::IndexMap;
 #[cfg(not(feature = "indexmap"))]
 use std::collections::HashMap;
-use crate::helpers::stack::dctypes::*;
+use crate::helpers::project::dctypes::*;
 
 #[cfg(feature = "indexmap")]
 #[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
diff --git a/src/helpers/stack/dctypes/build_args.rs b/src/helpers/project/dctypes/build_args.rs
similarity index 100%
rename from src/helpers/stack/dctypes/build_args.rs
rename to src/helpers/project/dctypes/build_args.rs
diff --git a/src/helpers/stack/dctypes/build_step.rs b/src/helpers/project/dctypes/build_step.rs
similarity index 84%
rename from src/helpers/stack/dctypes/build_step.rs
rename to src/helpers/project/dctypes/build_step.rs
index 25a468d6..3c1fc49f 100644
--- a/src/helpers/stack/dctypes/build_step.rs
+++ b/src/helpers/project/dctypes/build_step.rs
@@ -1,5 +1,5 @@
 use serde::{Deserialize, Serialize};
-use crate::helpers::stack::dctypes;
+use crate::helpers::project::dctypes;
 
 #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
 #[serde(untagged)]
diff --git a/src/helpers/stack/dctypes/compose.rs b/src/helpers/project/dctypes/compose.rs
similarity index 96%
rename from src/helpers/stack/dctypes/compose.rs
rename to src/helpers/project/dctypes/compose.rs
index 46d43c27..4bee50a0 100644
--- a/src/helpers/stack/dctypes/compose.rs
+++ b/src/helpers/project/dctypes/compose.rs
@@ -1,5 +1,5 @@
 use serde::{Deserialize, Serialize};
-use crate::helpers::stack::dctypes::*;
+use crate::helpers::project::dctypes::*;
 #[cfg(feature = "indexmap")]
 use indexmap::IndexMap;
 use serde_yaml::Value;
diff --git a/src/helpers/stack/dctypes/compose_file.rs b/src/helpers/project/dctypes/compose_file.rs
similarity index 92%
rename from src/helpers/stack/dctypes/compose_file.rs
rename to src/helpers/project/dctypes/compose_file.rs
index 1131f675..79abe918 100644
--- a/src/helpers/stack/dctypes/compose_file.rs
+++ b/src/helpers/project/dctypes/compose_file.rs
@@ -1,5 +1,5 @@
 use serde::{Deserialize, Serialize};
-use crate::helpers::stack::dctypes;
+use crate::helpers::project::dctypes;
 
 #[cfg(feature = "indexmap")]
 use indexmap::IndexMap;
diff --git a/src/helpers/stack/dctypes/compose_network.rs b/src/helpers/project/dctypes/compose_network.rs
similarity index 83%
rename from src/helpers/stack/dctypes/compose_network.rs
rename to src/helpers/project/dctypes/compose_network.rs
index da364349..56d3de15 100644
--- a/src/helpers/stack/dctypes/compose_network.rs
+++ b/src/helpers/project/dctypes/compose_network.rs
@@ -1,5 +1,5 @@
 use serde::{Deserialize, Serialize};
-use crate::helpers::stack::dctypes::*;
+use crate::helpers::project::dctypes::*;
 
 #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
 #[serde(untagged)]
diff --git a/src/helpers/stack/dctypes/compose_network_setting_details.rs b/src/helpers/project/dctypes/compose_network_setting_details.rs
similarity index 100%
rename from src/helpers/stack/dctypes/compose_network_setting_details.rs
rename to src/helpers/project/dctypes/compose_network_setting_details.rs
diff --git a/src/helpers/stack/dctypes/compose_networks.rs b/src/helpers/project/dctypes/compose_networks.rs
similarity index 93%
rename from src/helpers/stack/dctypes/compose_networks.rs
rename to src/helpers/project/dctypes/compose_networks.rs
index f1374f0c..da6aae11 100644
--- a/src/helpers/stack/dctypes/compose_networks.rs
+++ b/src/helpers/project/dctypes/compose_networks.rs
@@ -3,7 +3,7 @@ use indexmap::IndexMap;
 #[cfg(not(feature = "indexmap"))]
 use std::collections::HashMap;
 use serde::{Deserialize, Serialize};
-use crate::helpers::stack::dctypes::*;
+use crate::helpers::project::dctypes::*;
 
 #[cfg(feature = "indexmap")]
 #[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
diff --git a/src/helpers/stack/dctypes/compose_volume.rs b/src/helpers/project/dctypes/compose_volume.rs
similarity index 79%
rename from src/helpers/stack/dctypes/compose_volume.rs
rename to src/helpers/project/dctypes/compose_volume.rs
index 4d40b23f..644b67cf 100644
--- a/src/helpers/stack/dctypes/compose_volume.rs
+++ b/src/helpers/project/dctypes/compose_volume.rs
@@ -1,5 +1,5 @@
 use serde::{Deserialize, Serialize};
-use crate::helpers::stack::dctypes::*;
+use crate::helpers::project::dctypes::*;
 #[cfg(feature = "indexmap")]
 use indexmap::IndexMap;
 #[cfg(not(feature = "indexmap"))]
@@ -12,9 +12,6 @@ pub struct ComposeVolume {
     #[cfg(feature = "indexmap")]
     #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
     pub driver_opts: IndexMap<String, Option<SingleValue>>,
-    #[cfg(not(feature = "indexmap"))]
-    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
-    pub driver_opts: HashMap<String, Option<SingleValue>>,
     #[serde(skip_serializing_if = "Option::is_none")]
     pub external: Option<ExternalVolume>,
     #[serde(default, skip_serializing_if = "Labels::is_empty")]
diff --git a/src/helpers/stack/dctypes/depends_condition.rs b/src/helpers/project/dctypes/depends_condition.rs
similarity index 100%
rename from src/helpers/stack/dctypes/depends_condition.rs
rename to src/helpers/project/dctypes/depends_condition.rs
diff --git a/src/helpers/stack/dctypes/depends_on_options.rs b/src/helpers/project/dctypes/depends_on_options.rs
similarity index 95%
rename from src/helpers/stack/dctypes/depends_on_options.rs
rename to src/helpers/project/dctypes/depends_on_options.rs
index 6e12300b..fbf79119 100644
--- a/src/helpers/stack/dctypes/depends_on_options.rs
+++ b/src/helpers/project/dctypes/depends_on_options.rs
@@ -1,7 +1,7 @@
 use serde::{Deserialize, Serialize};
 #[cfg(feature = "indexmap")]
 use indexmap::IndexMap;
-use crate::helpers::stack::dctypes;
+use crate::helpers::project::dctypes;
 
 #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
 #[serde(untagged)]
diff --git a/src/helpers/stack/dctypes/env_file.rs b/src/helpers/project/dctypes/env_file.rs
similarity index 100%
rename from src/helpers/stack/dctypes/env_file.rs
rename to src/helpers/project/dctypes/env_file.rs
diff --git a/src/helpers/stack/dctypes/environment.rs b/src/helpers/project/dctypes/environment.rs
similarity index 94%
rename from src/helpers/stack/dctypes/environment.rs
rename to src/helpers/project/dctypes/environment.rs
index 3afafed6..a04f9920 100644
--- a/src/helpers/stack/dctypes/environment.rs
+++ b/src/helpers/project/dctypes/environment.rs
@@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
 use serde_yaml::Value;
 #[cfg(feature = "indexmap")]
 use indexmap::IndexMap;
-use crate::helpers::stack::dctypes;
+use crate::helpers::project::dctypes;
 
 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
 #[serde(untagged)]
diff --git a/src/helpers/stack/dctypes/extension.rs b/src/helpers/project/dctypes/extension.rs
similarity index 94%
rename from src/helpers/stack/dctypes/extension.rs
rename to src/helpers/project/dctypes/extension.rs
index f70871c5..1c0b412d 100644
--- a/src/helpers/stack/dctypes/extension.rs
+++ b/src/helpers/project/dctypes/extension.rs
@@ -1,5 +1,5 @@
 use serde::{Deserialize, Serialize};
-use crate::helpers::stack::dctypes;
+use crate::helpers::project::dctypes;
 use std::str::FromStr;
 
 #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default, Ord, PartialOrd)]
diff --git a/src/helpers/stack/dctypes/extension_parse_error.rs b/src/helpers/project/dctypes/extension_parse_error.rs
similarity index 100%
rename from src/helpers/stack/dctypes/extension_parse_error.rs
rename to src/helpers/project/dctypes/extension_parse_error.rs
diff --git a/src/helpers/stack/dctypes/external_network_setting_bool.rs b/src/helpers/project/dctypes/external_network_setting_bool.rs
similarity index 100%
rename from src/helpers/stack/dctypes/external_network_setting_bool.rs
rename to src/helpers/project/dctypes/external_network_setting_bool.rs
diff --git a/src/helpers/stack/dctypes/external_volume.rs b/src/helpers/project/dctypes/external_volume.rs
similarity index 100%
rename from src/helpers/stack/dctypes/external_volume.rs
rename to src/helpers/project/dctypes/external_volume.rs
diff --git a/src/helpers/stack/dctypes/ipam.rs b/src/helpers/project/dctypes/ipam.rs
similarity index 89%
rename from src/helpers/stack/dctypes/ipam.rs
rename to src/helpers/project/dctypes/ipam.rs
index 592c2a6d..99e8b1f1 100644
--- a/src/helpers/stack/dctypes/ipam.rs
+++ b/src/helpers/project/dctypes/ipam.rs
@@ -1,5 +1,5 @@
 use serde::{Deserialize, Serialize};
-use crate::helpers::stack::dctypes::*;
+use crate::helpers::project::dctypes::*;
 
 #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
 #[serde(deny_unknown_fields)]
diff --git a/src/helpers/stack/dctypes/ipam_config.rs b/src/helpers/project/dctypes/ipam_config.rs
similarity index 100%
rename from src/helpers/stack/dctypes/ipam_config.rs
rename to src/helpers/project/dctypes/ipam_config.rs
diff --git a/src/helpers/stack/dctypes/labels.rs b/src/helpers/project/dctypes/labels.rs
similarity index 100%
rename from src/helpers/stack/dctypes/labels.rs
rename to src/helpers/project/dctypes/labels.rs
diff --git a/src/helpers/stack/dctypes/logging_parameters.rs b/src/helpers/project/dctypes/logging_parameters.rs
similarity index 93%
rename from src/helpers/stack/dctypes/logging_parameters.rs
rename to src/helpers/project/dctypes/logging_parameters.rs
index bae66fcb..a0068c7b 100644
--- a/src/helpers/stack/dctypes/logging_parameters.rs
+++ b/src/helpers/project/dctypes/logging_parameters.rs
@@ -1,7 +1,7 @@
 use serde::{Deserialize, Serialize};
 #[cfg(feature = "indexmap")]
 use indexmap::IndexMap;
-use crate::helpers::stack::dctypes;
+use crate::helpers::project::dctypes;
 
 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
 pub struct LoggingParameters {
diff --git a/src/helpers/stack/dctypes/mod.rs b/src/helpers/project/dctypes/mod.rs
similarity index 99%
rename from src/helpers/stack/dctypes/mod.rs
rename to src/helpers/project/dctypes/mod.rs
index 66bda096..093c7f17 100644
--- a/src/helpers/stack/dctypes/mod.rs
+++ b/src/helpers/project/dctypes/mod.rs
@@ -72,7 +72,7 @@ pub use network_settings::*;
 pub use ipam::*;
 pub use ipam_config::*;
 
-use crate::helpers::stack::dctypes;
+use crate::helpers::project::dctypes;
 
 use derive_builder::*;
 #[cfg(feature = "indexmap")]
diff --git a/src/helpers/stack/dctypes/network_settings.rs b/src/helpers/project/dctypes/network_settings.rs
similarity index 96%
rename from src/helpers/stack/dctypes/network_settings.rs
rename to src/helpers/project/dctypes/network_settings.rs
index 9c45bc15..72b27b29 100644
--- a/src/helpers/stack/dctypes/network_settings.rs
+++ b/src/helpers/project/dctypes/network_settings.rs
@@ -1,5 +1,5 @@
 use serde::{Deserialize, Serialize};
-use crate::helpers::stack::dctypes::*;
+use crate::helpers::project::dctypes::*;
 
 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
 #[serde(deny_unknown_fields)]
diff --git a/src/helpers/stack/dctypes/networks.rs b/src/helpers/project/dctypes/networks.rs
similarity index 92%
rename from src/helpers/stack/dctypes/networks.rs
rename to src/helpers/project/dctypes/networks.rs
index bbcfdb14..751554fb 100644
--- a/src/helpers/stack/dctypes/networks.rs
+++ b/src/helpers/project/dctypes/networks.rs
@@ -1,5 +1,5 @@
 use serde::{Deserialize, Serialize};
-use crate::helpers::stack::dctypes;
+use crate::helpers::project::dctypes;
 
 #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
 #[serde(untagged)]
diff --git a/src/helpers/stack/dctypes/port.rs b/src/helpers/project/dctypes/port.rs
similarity index 94%
rename from src/helpers/stack/dctypes/port.rs
rename to src/helpers/project/dctypes/port.rs
index 25557581..fc437ef4 100644
--- a/src/helpers/stack/dctypes/port.rs
+++ b/src/helpers/project/dctypes/port.rs
@@ -1,5 +1,5 @@
 use serde::{Deserialize, Serialize};
-use crate::helpers::stack::dctypes;
+use crate::helpers::project::dctypes;
 use crate::forms;
 
 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
@@ -27,7 +27,7 @@ impl Default for Port {
     }
 }
 
-impl TryInto<Port> for &forms::stack::Port {
+impl TryInto<Port> for &forms::project::Port {
     type Error = String;
     fn try_into(self) -> Result<Port, Self::Error> {
         let cp = self
diff --git a/src/helpers/stack/dctypes/ports.rs b/src/helpers/project/dctypes/ports.rs
similarity index 92%
rename from src/helpers/stack/dctypes/ports.rs
rename to src/helpers/project/dctypes/ports.rs
index bdcc6a81..3c61494e 100644
--- a/src/helpers/stack/dctypes/ports.rs
+++ b/src/helpers/project/dctypes/ports.rs
@@ -1,5 +1,5 @@
 use serde::{Deserialize, Serialize};
-use crate::helpers::stack::dctypes;
+use crate::helpers::project::dctypes;
 
 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
 #[serde(untagged)]
diff --git a/src/helpers/stack/dctypes/published_port.rs b/src/helpers/project/dctypes/published_port.rs
similarity index 100%
rename from src/helpers/stack/dctypes/published_port.rs
rename to src/helpers/project/dctypes/published_port.rs
diff --git a/src/helpers/stack/dctypes/service.rs b/src/helpers/project/dctypes/service.rs
similarity index 99%
rename from src/helpers/stack/dctypes/service.rs
rename to src/helpers/project/dctypes/service.rs
index 4cebefcf..e288632f 100644
--- a/src/helpers/stack/dctypes/service.rs
+++ b/src/helpers/project/dctypes/service.rs
@@ -1,7 +1,7 @@
 use serde::{Deserialize, Serialize};
 #[cfg(feature = "indexmap")]
 use indexmap::IndexMap;
-use crate::helpers::stack::dctypes;
+use crate::helpers::project::dctypes;
 use serde_json::Value;
 use derive_builder::*;
 
diff --git a/src/helpers/stack/dctypes/services.rs b/src/helpers/project/dctypes/services.rs
similarity index 93%
rename from src/helpers/stack/dctypes/services.rs
rename to src/helpers/project/dctypes/services.rs
index b41ff10d..949437e8 100644
--- a/src/helpers/stack/dctypes/services.rs
+++ b/src/helpers/project/dctypes/services.rs
@@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
 use indexmap::IndexMap;
 #[cfg(not(feature = "indexmap"))]
 use std::collections::HashMap;
-use crate::helpers::stack::dctypes;
+use crate::helpers::project::dctypes;
 
 #[cfg(feature = "indexmap")]
 #[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
diff --git a/src/helpers/stack/dctypes/single_service.rs b/src/helpers/project/dctypes/single_service.rs
similarity index 81%
rename from src/helpers/stack/dctypes/single_service.rs
rename to src/helpers/project/dctypes/single_service.rs
index 489a3063..19031c5a 100644
--- a/src/helpers/stack/dctypes/single_service.rs
+++ b/src/helpers/project/dctypes/single_service.rs
@@ -1,5 +1,5 @@
 use serde::{Deserialize, Serialize};
-use crate::helpers::stack::dctypes;
+use crate::helpers::project::dctypes;
 
 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
 pub struct SingleService {
diff --git a/src/helpers/stack/dctypes/sys_ctls.rs b/src/helpers/project/dctypes/sys_ctls.rs
similarity index 94%
rename from src/helpers/stack/dctypes/sys_ctls.rs
rename to src/helpers/project/dctypes/sys_ctls.rs
index 45d0a7c3..4abbb24e 100644
--- a/src/helpers/stack/dctypes/sys_ctls.rs
+++ b/src/helpers/project/dctypes/sys_ctls.rs
@@ -1,5 +1,5 @@
 use serde::{Deserialize, Serialize};
-use crate::helpers::stack::dctypes;
+use crate::helpers::project::dctypes;
 #[cfg(feature = "indexmap")]
 use indexmap::IndexMap;
 
diff --git a/src/helpers/stack/dctypes/tmpfs.rs b/src/helpers/project/dctypes/tmpfs.rs
similarity index 100%
rename from src/helpers/stack/dctypes/tmpfs.rs
rename to src/helpers/project/dctypes/tmpfs.rs
diff --git a/src/helpers/stack/dctypes/top_level_volumes.rs b/src/helpers/project/dctypes/top_level_volumes.rs
similarity index 93%
rename from src/helpers/stack/dctypes/top_level_volumes.rs
rename to src/helpers/project/dctypes/top_level_volumes.rs
index 7216953d..304d531d 100644
--- a/src/helpers/stack/dctypes/top_level_volumes.rs
+++ b/src/helpers/project/dctypes/top_level_volumes.rs
@@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
 use indexmap::IndexMap;
 #[cfg(not(feature = "indexmap"))]
 use std::collections::HashMap;
-use crate::helpers::stack::dctypes::*;
+use crate::helpers::project::dctypes::*;
 
 #[cfg(feature = "indexmap")]
 #[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
diff --git a/src/helpers/stack/dctypes/ulimit.rs b/src/helpers/project/dctypes/ulimit.rs
similarity index 100%
rename from src/helpers/stack/dctypes/ulimit.rs
rename to src/helpers/project/dctypes/ulimit.rs
diff --git a/src/helpers/stack/dctypes/ulimits.rs b/src/helpers/project/dctypes/ulimits.rs
similarity index 93%
rename from src/helpers/stack/dctypes/ulimits.rs
rename to src/helpers/project/dctypes/ulimits.rs
index 721eaf96..2cfe4f51 100644
--- a/src/helpers/stack/dctypes/ulimits.rs
+++ b/src/helpers/project/dctypes/ulimits.rs
@@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
 use indexmap::IndexMap;
 #[cfg(not(feature = "indexmap"))]
 use std::collections::HashMap;
-use crate::helpers::stack::dctypes;
+use crate::helpers::project::dctypes;
 
 #[cfg(feature = "indexmap")]
 #[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
diff --git a/src/helpers/stack/mod.rs b/src/helpers/project/mod.rs
similarity index 100%
rename from src/helpers/stack/mod.rs
rename to src/helpers/project/mod.rs
diff --git a/src/models/cloud.rs b/src/models/cloud.rs
new file mode 100644
index 00000000..308950dd
--- /dev/null
+++ b/src/models/cloud.rs
@@ -0,0 +1,15 @@
+use chrono::{DateTime, Utc};
+use serde_derive::{Deserialize, Serialize};
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct Cloud {
+    pub id: i32,
+    pub user_id: String,
+    pub provider: String,
+    pub cloud_token: Option<String>,
+    pub cloud_key: Option<String>,
+    pub cloud_secret: Option<String>,
+    pub save_token: Option<bool>,
+    pub created_at: DateTime<Utc>,
+    pub updated_at: DateTime<Utc>,
+}
diff --git a/src/models/deployment.rs b/src/models/deployment.rs
new file mode 100644
index 00000000..3aea4e64
--- /dev/null
+++ b/src/models/deployment.rs
@@ -0,0 +1,38 @@
+use chrono::{DateTime, Utc};
+use serde::{Deserialize, Serialize};
+use serde_json::Value;
+
+// Store user deployment attempts for a specific project
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct Deployment {
+    pub id: i32,         // id - is a unique identifier for the app project
+    pub project_id: i32,  // external project ID
+    pub deleted: bool,
+    pub status: String,
+    pub body: Value, //json type
+    pub created_at: DateTime<Utc>,
+    pub updated_at: DateTime<Utc>,
+}
+
+impl Deployment {
+    pub fn new(project_id: i32, status: String, body: Value) -> Self {
+        Self {
+            id: 0,
+            project_id,
+            deleted: false,
+            status,
+            body,
+            created_at: Utc::now(),
+            updated_at: Utc::now(),
+        }
+    }
+}
+
+impl Default for Deployment {
+    fn default() -> Self {
+        Deployment {
+            status: "pending".to_string(),
+            ..Default::default()
+        }
+    }
+}
diff --git a/src/models/mod.rs b/src/models/mod.rs
index a17ea327..03de8abd 100644
--- a/src/models/mod.rs
+++ b/src/models/mod.rs
@@ -3,13 +3,19 @@ mod product;
 mod ratecategory;
 mod rules;
 pub mod rating;
-pub mod stack;
+pub mod project;
 pub mod user;
+mod deployment;
+mod cloud;
+mod server;
 
 pub use client::*;
 pub use rating::*;
-pub use stack::*;
+pub use project::*;
 pub use user::*;
 pub use product::*;
 pub use ratecategory::*;
 pub use rules::*;
+pub use deployment::*;
+pub use cloud::*;
+pub use server::*;
diff --git a/src/models/product.rs b/src/models/product.rs
index 992818db..8fde4f3d 100644
--- a/src/models/product.rs
+++ b/src/models/product.rs
@@ -2,15 +2,15 @@ use chrono::{DateTime, Utc};
 
 pub struct Product {
     // Product - is an external object that we want to store in the database,
-    // that can be a stack or an app in the stack. feature, service, web app etc.
+    // that can be a project or an app in the project. feature, service, web app etc.
     // id - is a unique identifier for the product
     // user_id - is a unique identifier for the user
     // rating - is a rating of the product
-    // product type stack & app,
+    // product type project & app,
     // id is generated based on the product type and external obj_id
     pub id: i32,          //primary key, for better data management
     pub obj_id: i32,      // external product ID db, no autoincrement, example: 100
-    pub obj_type: String, // stack | app, unique index
+    pub obj_type: String, // project | app, unique index
     pub created_at: DateTime<Utc>,
     pub updated_at: DateTime<Utc>,
 }
diff --git a/src/models/stack.rs b/src/models/project.rs
similarity index 79%
rename from src/models/stack.rs
rename to src/models/project.rs
index a808f130..35661eb2 100644
--- a/src/models/stack.rs
+++ b/src/models/project.rs
@@ -4,9 +4,10 @@ use serde_json::Value;
 use uuid::Uuid;
 
 #[derive(Debug, Serialize, Deserialize, Clone)]
-pub struct Stack {
-    pub id: i32,         // id - is a unique identifier for the app stack
-    pub stack_id: Uuid,  // external stack ID
+pub struct Project {
+    pub id: i32,         // id - is a unique identifier for the app project
+    pub cloud_id: Option<i32>,   // cloud assigned to a project
+    pub stack_id: Uuid,  // external project ID
     pub user_id: String, // external unique identifier for the user
     pub name: String,
     // pub body: sqlx::types::Json<String>,
@@ -15,11 +16,12 @@ pub struct Stack {
     pub updated_at: DateTime<Utc>,
 }
 
-impl Stack {
+impl Project {
     pub fn new(user_id: String, name: String, body: Value) -> Self {
         Self {
             id: 0,
             stack_id: Uuid::new_v4(),
+            cloud_id: None,
             user_id: user_id,
             name: name,
             body: body,
@@ -29,9 +31,9 @@ impl Stack {
     }
 }
 
-impl Default for Stack {
+impl Default for Project {
     fn default() -> Self {
-        Stack {
+        Project {
             user_id: "".to_string(),
             name: "".to_string(),
             ..Default::default()
diff --git a/src/models/ratecategory.rs b/src/models/ratecategory.rs
index 8228ae0e..352bedbc 100644
--- a/src/models/ratecategory.rs
+++ b/src/models/ratecategory.rs
@@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
 pub enum RateCategory {
     Application, // app, feature, extension
     Cloud,       // is user satisfied working with this cloud
-    Stack,       // app stack
+    Project,       // app project
     DeploymentSpeed,
     Documentation,
     Design,
diff --git a/src/models/server.rs b/src/models/server.rs
new file mode 100644
index 00000000..6098d68e
--- /dev/null
+++ b/src/models/server.rs
@@ -0,0 +1,28 @@
+use chrono::{DateTime, Utc};
+use serde_derive::{Deserialize, Serialize};
+use serde_valid::Validate;
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct Server {
+    pub id: i32,
+    pub user_id: String,
+    pub cloud_id: i32,
+    pub project_id: i32,
+    #[validate(min_length = 2)]
+    #[validate(max_length = 50)]
+    pub region: String,
+    #[validate(min_length = 2)]
+    #[validate(max_length = 50)]
+    pub zone: Option<String>,
+    #[validate(min_length = 2)]
+    #[validate(max_length = 50)]
+    pub server: String,
+    #[validate(min_length = 2)]
+    #[validate(max_length = 50)]
+    pub os: String,
+    #[validate(min_length = 3)]
+    #[validate(max_length = 50)]
+    pub disk_type: Option<String>,
+    pub created_at: DateTime<Utc>,
+    pub updated_at: DateTime<Utc>,
+}
diff --git a/src/routes/cloud/add.rs b/src/routes/cloud/add.rs
new file mode 100644
index 00000000..4923cad9
--- /dev/null
+++ b/src/routes/cloud/add.rs
@@ -0,0 +1,50 @@
+use crate::forms;
+use crate::helpers::JsonResponse;
+use crate::models;
+use crate::db;
+use actix_web::{post, web, Responder, Result};
+use sqlx::PgPool;
+use tracing::Instrument;
+use std::sync::Arc;
+use serde_valid::Validate;
+
+// workflow
+// add, update, list, get(user_id), ACL,
+// ACL - access to func for a user
+// ACL - access to objects for a user
+
+#[tracing::instrument(name = "Add cloud.")]
+#[post("")]
+pub async fn add(
+    user: web::ReqData<Arc<models::User>>,
+    form: web::Json<forms::cloud::Cloud>,
+    pg_pool: web::Data<PgPool>,
+) -> Result<impl Responder> {
+
+    if !form.validate().is_ok() {
+        let errors = form.validate().unwrap_err().to_string();
+        let err_msg = format!("Invalid data received {:?}", &errors);
+        tracing::debug!(err_msg);
+
+        return Err(JsonResponse::<models::Project>::build().form_error(errors));
+    }
+
+    let cloud = db::cloud::fetch(pg_pool.get_ref(), form.id)
+        .await
+        .map_err(|_msg| JsonResponse::<models::Cloud>::build().internal_server_error(_msg))?
+        .ok_or_else(|| JsonResponse::<models::Cloud>::build().not_found("not found"))?
+        ;
+
+    tracing::debug!("Cloud record is found ? {:?}", cloud);
+
+    let mut cloud: models::Cloud = form.into_inner().into();
+    cloud.user_id = user.id.clone();
+
+    db::cloud::insert(pg_pool.get_ref(), cloud)
+        .await
+        .map(|cloud| JsonResponse::build()
+            .set_item(cloud)
+            .ok("success"))
+        .map_err(|_err| JsonResponse::<models::Cloud>::build()
+            .internal_server_error("Failed to insert"))
+}
diff --git a/src/routes/cloud/get.rs b/src/routes/cloud/get.rs
new file mode 100644
index 00000000..62f2a472
--- /dev/null
+++ b/src/routes/cloud/get.rs
@@ -0,0 +1,46 @@
+use std::sync::Arc;
+use crate::db;
+use crate::helpers::JsonResponse;
+use crate::models;
+use actix_web::{get, web, Responder, Result};
+use sqlx::PgPool;
+use tracing::Instrument;
+
+// workflow
+// add, update, list, get(user_id), ACL,
+// ACL - access to func for a user
+// ACL - access to objects for a user
+
+#[tracing::instrument(name = "Get cloud.")]
+#[get("/{id}")]
+pub async fn item(
+    path: web::Path<(i32,)>,
+    pg_pool: web::Data<PgPool>,
+) -> Result<impl Responder> {
+    let id = path.0;
+    let cloud = db::cloud::fetch(pg_pool.get_ref(), id)
+        .await
+        .map_err(|_err| JsonResponse::<models::Cloud>::build()
+            .internal_server_error(""))
+        .and_then(|cloud| {
+            match cloud {
+                Some(cloud) => { Ok(cloud) },
+                None => Err(JsonResponse::<models::Cloud>::build().not_found("object not found"))
+            }
+        })?;
+
+    Ok(JsonResponse::build().set_item(cloud).ok("OK"))
+}
+
+#[tracing::instrument(name = "Get all clouds.")]
+#[get("")]
+pub async fn list(
+    path: web::Path<()>,
+    user: web::ReqData<Arc<models::User>>,
+    pg_pool: web::Data<PgPool>,
+) -> Result<impl Responder> {
+    db::cloud::fetch_by_user(pg_pool.get_ref(), user.id.as_ref())
+        .await
+        .map(|clouds| JsonResponse::build().set_list(clouds).ok("OK"))
+        .map_err(|_err| JsonResponse::<models::Cloud>::build().internal_server_error(""))
+}
diff --git a/src/routes/cloud/mod.rs b/src/routes/cloud/mod.rs
new file mode 100644
index 00000000..a489a06d
--- /dev/null
+++ b/src/routes/cloud/mod.rs
@@ -0,0 +1,7 @@
+pub mod add;
+pub mod get;
+pub mod update;
+
+pub use add::*;
+pub use get::*;
+pub use update::*;
diff --git a/src/routes/stack/service.rs b/src/routes/cloud/update.rs
similarity index 100%
rename from src/routes/stack/service.rs
rename to src/routes/cloud/update.rs
diff --git a/src/routes/mod.rs b/src/routes/mod.rs
index 0d6e99e8..647742a3 100644
--- a/src/routes/mod.rs
+++ b/src/routes/mod.rs
@@ -4,5 +4,8 @@ pub(crate) mod rating;
 pub(crate) mod test;
 
 pub use health_checks::*;
-pub(crate) mod stack;
-pub use stack::*;
+pub(crate) mod project;
+pub(crate) mod cloud;
+pub(crate) mod server;
+
+pub use project::*;
diff --git a/src/routes/project/add.rs b/src/routes/project/add.rs
new file mode 100644
index 00000000..e3d1b076
--- /dev/null
+++ b/src/routes/project/add.rs
@@ -0,0 +1,78 @@
+use crate::db;
+use crate::forms;
+use crate::helpers::JsonResponse;
+use crate::models;
+use actix_web::Error;
+use actix_web::{
+    post, web,
+    web::{Bytes, Data},
+    Responder, Result,
+};
+use serde_json::Value;
+use serde_valid::Validate;
+use sqlx::PgPool;
+use std::str;
+use std::sync::Arc;
+
+#[tracing::instrument(name = "Add project.")]
+#[post("")]
+pub async fn add(
+    body: Bytes,
+    user: web::ReqData<Arc<models::User>>,
+    pg_pool: Data<PgPool>,
+) -> Result<impl Responder> {
+    // @todo ACL
+    let form = body_into_form(body).await?;
+    let project_name = form.custom.custom_stack_code.clone();
+
+    project_exists(pg_pool.get_ref(), &project_name).await?;
+
+    let body: Value = serde_json::to_value::<forms::project::ProjectForm>(form)
+        .or(serde_json::to_value::<forms::project::ProjectForm>(forms::project::ProjectForm::default()))
+        .unwrap();
+
+    let project = models::Project::new(user.id.clone(), project_name, body);
+    db::project::insert(pg_pool.get_ref(), project)
+        .await
+        .map(|project| JsonResponse::build().set_item(project).ok("Ok"))
+        .map_err(|_| {
+            JsonResponse::<models::Project>::build().internal_server_error("Internal Server Error")
+        })
+}
+
+
+async fn project_exists(pool: &PgPool, project_name: &String) -> Result<(), Error> {
+    db::project::fetch_one_by_name(pool, project_name)
+        .await
+        .map_err(|_| {
+            JsonResponse::<models::Project>::build().internal_server_error("Internal Server Error")
+        })
+        .and_then(|project| match project {
+            Some(_) => Err(JsonResponse::<models::Project>::build()
+                .conflict("Project with that name already exists")),
+            None => Ok(()),
+        })
+}
+
+async fn body_into_form(body: Bytes) -> Result<forms::project::ProjectForm, Error> {
+    let body_bytes = actix_web::body::to_bytes(body).await.unwrap();
+    let body_str = str::from_utf8(&body_bytes)
+        .map_err(|err| JsonResponse::<forms::project::ProjectForm>::build().internal_server_error(err.to_string()))?;
+    let deserializer = &mut serde_json::Deserializer::from_str(body_str);
+    serde_path_to_error::deserialize(deserializer)
+        .map_err(|err| {
+            let msg = format!("{}:{:?}", err.path().to_string(), err);
+            JsonResponse::<forms::project::ProjectForm>::build().bad_request(msg)
+        })
+        .and_then(|form: forms::project::ProjectForm| {
+            if !form.validate().is_ok() {
+                let errors = form.validate().unwrap_err().to_string();
+                let err_msg = format!("Invalid data received {:?}", &errors);
+                tracing::debug!(err_msg);
+
+                return Err(JsonResponse::<models::Project>::build().form_error(errors));
+            }
+
+            Ok(form)
+        })
+}
diff --git a/src/routes/stack/compose.rs b/src/routes/project/compose.rs
similarity index 50%
rename from src/routes/stack/compose.rs
rename to src/routes/project/compose.rs
index 7c926b57..d0ea4323 100644
--- a/src/routes/stack/compose.rs
+++ b/src/routes/project/compose.rs
@@ -1,5 +1,5 @@
 use crate::db;
-use crate::helpers::stack::builder::DcBuilder;
+use crate::helpers::project::builder::DcBuilder;
 use crate::helpers::JsonResponse;
 use crate::models;
 use actix_web::{get, web, web::Data, Responder, Result};
@@ -14,21 +14,21 @@ pub async fn add(
     pg_pool: Data<PgPool>,
 ) -> Result<impl Responder> {
     let id = path.0;
-    let stack = db::stack::fetch(pg_pool.get_ref(), id)
+    let project = db::project::fetch(pg_pool.get_ref(), id)
         .await
-        .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(err))
-        .and_then(|stack| match stack {
-            Some(stack) if stack.user_id != user.id => {
-                Err(JsonResponse::<models::Stack>::build().not_found("not found"))
+        .map_err(|err| JsonResponse::<models::Project>::build().internal_server_error(err))
+        .and_then(|project| match project {
+            Some(project) if project.user_id != user.id => {
+                Err(JsonResponse::<models::Project>::build().not_found("not found"))
             }
-            Some(stack) => Ok(stack),
-            None => Err(JsonResponse::<models::Stack>::build().not_found("not found")),
+            Some(project) => Ok(project),
+            None => Err(JsonResponse::<models::Project>::build().not_found("not found")),
         })?;
 
-    DcBuilder::new(stack)
+    DcBuilder::new(project)
         .build()
         .map_err(|err| {
-            JsonResponse::<models::Stack>::build().internal_server_error(err)
+            JsonResponse::<models::Project>::build().internal_server_error(err)
         })
         .map(|fc| JsonResponse::build().set_id(id).set_item(fc).ok("Success"))
 }
@@ -42,18 +42,18 @@ pub async fn admin(
 ) -> Result<impl Responder> {
     ///  Admin function for generating compose file for specified user
     let id = path.0;
-    let stack = db::stack::fetch(pg_pool.get_ref(), id)
+    let project = db::project::fetch(pg_pool.get_ref(), id)
         .await
-        .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(err))
-        .and_then(|stack| match stack {
-            Some(stack) => Ok(stack),
-            None => Err(JsonResponse::<models::Stack>::build().not_found("not found")),
+        .map_err(|err| JsonResponse::<models::Project>::build().internal_server_error(err))
+        .and_then(|project| match project {
+            Some(project) => Ok(project),
+            None => Err(JsonResponse::<models::Project>::build().not_found("not found")),
         })?;
 
-    DcBuilder::new(stack)
+    DcBuilder::new(project)
         .build()
         .map_err(|err| {
-            JsonResponse::<models::Stack>::build().internal_server_error(err)
+            JsonResponse::<models::Project>::build().internal_server_error(err)
         })
         .map(|fc| JsonResponse::build().set_id(id).set_item(fc).ok("Success"))
 }
diff --git a/src/routes/project/delete.rs b/src/routes/project/delete.rs
new file mode 100644
index 00000000..fb4ff336
--- /dev/null
+++ b/src/routes/project/delete.rs
@@ -0,0 +1,33 @@
+use crate::db;
+use crate::helpers::JsonResponse;
+use crate::models;
+use actix_web::{delete, web, Responder, Result};
+use sqlx::PgPool;
+use std::sync::Arc;
+use tracing::Instrument;
+
+#[tracing::instrument(name = "Get logged user project.")]
+#[delete("/{id}")]
+pub async fn item(
+    user: web::ReqData<Arc<models::User>>,
+    path: web::Path<(i32,)>,
+    pg_pool: web::Data<PgPool>,
+) -> Result<impl Responder> {
+    /// Get project apps of logged user only
+    let (id,) = path.into_inner();
+
+    db::project::fetch(pg_pool.get_ref(), id)
+        .await
+        .map_err(|err| JsonResponse::<models::Project>::build().internal_server_error(err))
+        .and_then(|project| match project {
+            Some(project) if project.user_id != user.id => {
+                Err(JsonResponse::<models::Project>::build().not_found("not found"))
+            }
+            Some(project) => {
+                db::project::delete(pg_pool.get_ref(), id);
+                Ok(JsonResponse::build().set_item(Some(project)).ok("Deleted"))
+            },
+            None => Err(JsonResponse::<models::Project>::build().not_found("not found")),
+        })
+
+}
diff --git a/src/routes/project/deploy.rs b/src/routes/project/deploy.rs
new file mode 100644
index 00000000..265957a0
--- /dev/null
+++ b/src/routes/project/deploy.rs
@@ -0,0 +1,79 @@
+use crate::configuration::Settings;
+use crate::db;
+use crate::forms;
+use crate::helpers::project::builder::DcBuilder;
+use crate::helpers::{JsonResponse, MqManager};
+use crate::models;
+use actix_web::{post, web, web::Data, Responder, Result};
+use sqlx::PgPool;
+use std::sync::Arc;
+use crate::helpers::compressor::compress;
+
+
+
+#[tracing::instrument(name = "Deploy for every user. Admin endpoint")]
+#[post("/{id}/deploy")]
+pub async fn add(
+    user: web::ReqData<Arc<models::User>>,
+    path: web::Path<(i32,)>,
+    pg_pool: Data<PgPool>,
+    mq_manager: Data<MqManager>,
+    sets: Data<Settings>,
+) -> Result<impl Responder> {
+    let id = path.0;
+    let project = db::project::fetch(pg_pool.get_ref(), id)
+        .await
+        .map_err(|err| JsonResponse::<models::Project>::build().internal_server_error(err))
+        .and_then(|project| match project {
+            Some(project) => Ok(project),
+            None => Err(JsonResponse::<models::Project>::build().not_found("not found")),
+        })?;
+
+    let id = project.id.clone();
+    let dc = DcBuilder::new(project);
+    let fc = dc.build().map_err(|err| {
+        JsonResponse::<models::Project>::build().internal_server_error(err)
+    })?;
+
+    let mut project_data = forms::project::Payload::try_from(&dc.project)
+        .map_err(|err| JsonResponse::<models::Project>::build().bad_request(err))?;
+    project_data.user_token = Some(user.id.clone());
+    project_data.user_email = Some(user.email.clone());
+    // let compressed = fc.unwrap_or("".to_string());
+    project_data.docker_compose = Some(compress(fc.as_str()));
+
+    // project_data.cloud =
+    // project_data.server =
+
+    let project_id = dc.project.id.clone();
+    let json_request = dc.project.body.clone();
+    let deployment = models::Deployment::new(
+        project_id,
+        String::from("pending"),
+        json_request
+    );
+
+    let result = db::deployment::insert(pg_pool.get_ref(), deployment)
+        .await
+        .map(|deployment| deployment)
+        .map_err(|_| {
+            JsonResponse::<models::Project>::build().internal_server_error("Internal Server Error")
+        });
+
+    tracing::debug!("Save deployment result: {:?}", result);
+
+    mq_manager
+        .publish_and_confirm(
+            "install".to_string(),
+            "install.start.tfa.all.all".to_string(),
+            &project_data,
+        )
+        .await
+        .map_err(|err| JsonResponse::<models::Project>::build().internal_server_error(err))
+        .map(|_| {
+            JsonResponse::<models::Project>::build()
+                .set_id(id)
+                .ok("Success")
+        })
+
+}
diff --git a/src/routes/project/get.rs b/src/routes/project/get.rs
new file mode 100644
index 00000000..3cd7fc3e
--- /dev/null
+++ b/src/routes/project/get.rs
@@ -0,0 +1,47 @@
+use crate::db;
+use crate::helpers::JsonResponse;
+use crate::models;
+use actix_web::{get, web, Responder, Result};
+use sqlx::PgPool;
+use std::sync::Arc;
+use tracing::Instrument;
+
+#[tracing::instrument(name = "Get logged user project.")]
+#[get("/{id}")]
+pub async fn item(
+    user: web::ReqData<Arc<models::User>>,
+    path: web::Path<(i32,)>,
+    pg_pool: web::Data<PgPool>,
+) -> Result<impl Responder> {
+    /// Get project apps of logged user only
+    let (id,) = path.into_inner();
+
+    db::project::fetch(pg_pool.get_ref(), id)
+        .await
+        .map_err(|err| JsonResponse::<models::Project>::build().internal_server_error(err))
+        .and_then(|project| match project {
+            Some(project) if project.user_id != user.id => {
+                Err(JsonResponse::<models::Project>::build().not_found("not found"))
+            }
+            Some(project) => Ok(JsonResponse::build().set_item(Some(project)).ok("OK")),
+            None => Err(JsonResponse::<models::Project>::build().not_found("not found")),
+        })
+}
+
+#[tracing::instrument(name = "Get user's project list.")]
+#[get("/user/{id}")]
+pub async fn list(
+    user: web::ReqData<Arc<models::User>>,
+    path: web::Path<(String,)>,
+    pg_pool: web::Data<PgPool>,
+) -> Result<impl Responder> {
+    /// This is admin endpoint, used by a client app, client app is confidential
+    /// it should return projects by user id
+    /// in order to pass validation at external deployment service
+    let user_id = path.into_inner().0;
+
+    db::project::fetch_by_user(pg_pool.get_ref(), &user_id)
+        .await
+        .map_err(|err| JsonResponse::<models::Project>::build().internal_server_error(err))
+        .map(|projects| JsonResponse::build().set_list(projects).ok("OK"))
+}
diff --git a/src/routes/stack/mod.rs b/src/routes/project/mod.rs
similarity index 92%
rename from src/routes/stack/mod.rs
rename to src/routes/project/mod.rs
index 27c80617..2d41c67d 100644
--- a/src/routes/stack/mod.rs
+++ b/src/routes/project/mod.rs
@@ -3,6 +3,7 @@ pub mod deploy;
 pub mod get;
 pub mod update;
 pub(crate) mod compose;
+mod delete;
 
 pub use add::*;
 pub use update::*;
diff --git a/src/services/stack.rs b/src/routes/project/service.rs
similarity index 100%
rename from src/services/stack.rs
rename to src/routes/project/service.rs
diff --git a/src/routes/project/update.rs b/src/routes/project/update.rs
new file mode 100644
index 00000000..5a8dac67
--- /dev/null
+++ b/src/routes/project/update.rs
@@ -0,0 +1,65 @@
+use crate::forms;
+use crate::helpers::JsonResponse;
+use crate::models;
+use crate::db;
+use actix_web::{web, web::Data, Responder, Result, post};
+use serde_json::Value;
+use serde_valid::Validate;
+use sqlx::PgPool;
+use std::sync::Arc;
+use tracing::Instrument;
+use uuid::Uuid;
+
+#[tracing::instrument(name = "Update project.")]
+#[post("/{id}")]
+pub async fn update(
+    path: web::Path<(i32,)>,
+    form: web::Json<forms::project::ProjectForm>,
+    user: web::ReqData<Arc<models::User>>,
+    pg_pool: Data<PgPool>,
+) -> Result<impl Responder> {
+    let id = path.0;
+    let mut project = db::project::fetch(pg_pool.get_ref(), id)
+        .await
+        .map_err(|err| JsonResponse::<models::Project>::build().internal_server_error(err))
+        .and_then(|project| match project {
+            Some(project) => Ok(project),
+            None => Err(JsonResponse::<models::Project>::build().not_found("Object not found")),
+        })?;
+
+    let project_name = form.custom.custom_stack_code.clone();
+    tracing::debug!("form data: {:?}", form);
+    let user_id = user.id.clone();
+
+    if let Err(errors) = form.validate() {
+        return Err(JsonResponse::<models::Project>::build().form_error(errors.to_string()));
+    }
+
+    let form_inner = form.into_inner();
+
+    if !form_inner.is_readable_docker_image().await.is_ok() {
+        return Err(JsonResponse::<models::Project>::build().bad_request("Can not access docker image"));
+    }
+
+    let body: Value = serde_json::to_value::<forms::project::ProjectForm>(form_inner)
+        .map_err(|err| 
+            JsonResponse::<models::Project>::build().bad_request(format!("{err}"))
+        )?; 
+
+    project.stack_id = Uuid::new_v4();
+    project.user_id = user_id;
+    project.name = project_name;
+    project.body = body;
+
+    db::project::update(pg_pool.get_ref(), project)
+        .await
+        .map(|project| {
+            JsonResponse::<models::Project>::build()
+                .set_item(project)
+                .ok("success")
+        })
+        .map_err(|err| {
+            tracing::error!("Failed to execute query: {:?}", err);
+            JsonResponse::<models::Project>::build().internal_server_error("")
+        })
+}
diff --git a/src/routes/server/add.rs b/src/routes/server/add.rs
new file mode 100644
index 00000000..6dbd2d06
--- /dev/null
+++ b/src/routes/server/add.rs
@@ -0,0 +1,50 @@
+use crate::forms;
+use crate::helpers::JsonResponse;
+use crate::models;
+use crate::db;
+use actix_web::{post, web, Responder, Result};
+use sqlx::PgPool;
+use tracing::Instrument;
+use std::sync::Arc;
+use serde_valid::Validate;
+
+// workflow
+// add, update, list, get(user_id), ACL,
+// ACL - access to func for a user
+// ACL - access to objects for a user
+
+#[tracing::instrument(name = "Add server.")]
+#[post("")]
+pub async fn add(
+    user: web::ReqData<Arc<models::User>>,
+    form: web::Json<forms::server::Server>,
+    pg_pool: web::Data<PgPool>,
+) -> Result<impl Responder> {
+
+    if !form.validate().is_ok() {
+        let errors = form.validate().unwrap_err().to_string();
+        let err_msg = format!("Invalid data received {:?}", &errors);
+        tracing::debug!(err_msg);
+
+        return Err(JsonResponse::<models::Project>::build().form_error(errors));
+    }
+
+    let server = db::server::fetch(pg_pool.get_ref(), form.id)
+        .await
+        .map_err(|_msg| JsonResponse::<models::Server>::build().internal_server_error(_msg))?
+        .ok_or_else(|| JsonResponse::<models::Server>::build().not_found("not found"))?
+        ;
+
+    tracing::debug!("Server record is found ? {:?}", server);
+
+    let mut server: models::Server = form.into_inner().into();
+    server.user_id = user.id.clone();
+
+    db::server::insert(pg_pool.get_ref(), server)
+        .await
+        .map(|server| JsonResponse::build()
+            .set_item(server)
+            .ok("success"))
+        .map_err(|_err| JsonResponse::<models::Server>::build()
+            .internal_server_error("Failed to insert"))
+}
diff --git a/src/routes/server/get.rs b/src/routes/server/get.rs
new file mode 100644
index 00000000..23bac5b5
--- /dev/null
+++ b/src/routes/server/get.rs
@@ -0,0 +1,46 @@
+use std::sync::Arc;
+use crate::db;
+use crate::helpers::JsonResponse;
+use crate::models;
+use actix_web::{get, web, Responder, Result};
+use sqlx::PgPool;
+use tracing::Instrument;
+
+// workflow
+// add, update, list, get(user_id), ACL,
+// ACL - access to func for a user
+// ACL - access to objects for a user
+
+#[tracing::instrument(name = "Get server.")]
+#[get("/{id}")]
+pub async fn item(
+    path: web::Path<(i32,)>,
+    pg_pool: web::Data<PgPool>,
+) -> Result<impl Responder> {
+    let id = path.0;
+    let server = db::server::fetch(pg_pool.get_ref(), id)
+        .await
+        .map_err(|_err| JsonResponse::<models::Server>::build()
+            .internal_server_error(""))
+        .and_then(|server| {
+            match server {
+                Some(server) => { Ok(server) },
+                None => Err(JsonResponse::<models::Server>::build().not_found("object not found"))
+            }
+        })?;
+
+    Ok(JsonResponse::build().set_item(server).ok("OK"))
+}
+
+#[tracing::instrument(name = "Get all servers.")]
+#[get("")]
+pub async fn list(
+    path: web::Path<()>,
+    user: web::ReqData<Arc<models::User>>,
+    pg_pool: web::Data<PgPool>,
+) -> Result<impl Responder> {
+    db::server::fetch_by_user(pg_pool.get_ref(), user.id.as_ref())
+        .await
+        .map(|server| JsonResponse::build().set_list(server).ok("OK"))
+        .map_err(|_err| JsonResponse::<models::Server>::build().internal_server_error(""))
+}
diff --git a/src/routes/server/mod.rs b/src/routes/server/mod.rs
new file mode 100644
index 00000000..d3122a38
--- /dev/null
+++ b/src/routes/server/mod.rs
@@ -0,0 +1,5 @@
+pub mod add;
+pub(crate) mod get;
+
+pub use add::*;
+pub use get::*;
diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs
deleted file mode 100644
index 712ca5a6..00000000
--- a/src/routes/stack/add.rs
+++ /dev/null
@@ -1,78 +0,0 @@
-use crate::db;
-use crate::forms;
-use crate::helpers::JsonResponse;
-use crate::models;
-use actix_web::Error;
-use actix_web::{
-    post, web,
-    web::{Bytes, Data},
-    Responder, Result,
-};
-use serde_json::Value;
-use serde_valid::Validate;
-use sqlx::PgPool;
-use std::str;
-use std::sync::Arc;
-
-#[tracing::instrument(name = "Add stack.")]
-#[post("")]
-pub async fn add(
-    body: Bytes,
-    user: web::ReqData<Arc<models::User>>,
-    pg_pool: Data<PgPool>,
-) -> Result<impl Responder> {
-    // @todo ACL
-    let form = body_into_form(body).await?;
-    let stack_name = form.custom.custom_stack_code.clone();
-
-    stack_exists(pg_pool.get_ref(), &stack_name).await?;
-
-    let body: Value = serde_json::to_value::<forms::stack::Stack>(form)
-        .or(serde_json::to_value::<forms::stack::Stack>(forms::stack::Stack::default()))
-        .unwrap();
-
-    let stack = models::Stack::new(user.id.clone(), stack_name, body);
-    db::stack::insert(pg_pool.get_ref(), stack)
-        .await
-        .map(|stack| JsonResponse::build().set_item(stack).ok("Ok"))
-        .map_err(|_| {
-            JsonResponse::<models::Stack>::build().internal_server_error("Internal Server Error")
-        })
-}
-
-
-async fn stack_exists(pool: &PgPool, stack_name: &String) -> Result<(), Error> {
-    db::stack::fetch_one_by_name(pool, stack_name)
-        .await
-        .map_err(|_| {
-            JsonResponse::<models::Stack>::build().internal_server_error("Internal Server Error")
-        })
-        .and_then(|stack| match stack {
-            Some(_) => Err(JsonResponse::<models::Stack>::build()
-                .conflict("Stack with that name already exists")),
-            None => Ok(()),
-        })
-}
-
-async fn body_into_form(body: Bytes) -> Result<forms::stack::Stack, Error> {
-    let body_bytes = actix_web::body::to_bytes(body).await.unwrap();
-    let body_str = str::from_utf8(&body_bytes)
-        .map_err(|err| JsonResponse::<forms::stack::Stack>::build().internal_server_error(err.to_string()))?;
-    let deserializer = &mut serde_json::Deserializer::from_str(body_str);
-    serde_path_to_error::deserialize(deserializer)
-        .map_err(|err| {
-            let msg = format!("{}:{:?}", err.path().to_string(), err);
-            JsonResponse::<forms::stack::Stack>::build().bad_request(msg)
-        })
-        .and_then(|form: forms::stack::Stack| {
-            if !form.validate().is_ok() {
-                let errors = form.validate().unwrap_err().to_string();
-                let err_msg = format!("Invalid data received {:?}", &errors);
-                tracing::debug!(err_msg);
-
-                return Err(JsonResponse::<models::Stack>::build().form_error(errors));
-            }
-
-            Ok(form)
-        })
-}
diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs
deleted file mode 100644
index a2bf8693..00000000
--- a/src/routes/stack/deploy.rs
+++ /dev/null
@@ -1,58 +0,0 @@
-use crate::configuration::Settings;
-use crate::db;
-use crate::forms;
-use crate::helpers::stack::builder::DcBuilder;
-use crate::helpers::{JsonResponse, MqManager};
-use crate::models;
-use actix_web::{post, web, web::Data, Responder, Result};
-use lapin::publisher_confirm::Confirmation;
-use sqlx::PgPool;
-use std::sync::Arc;
-use crate::helpers::compressor::compress;
-
-
-#[tracing::instrument(name = "Deploy for every user. Admin endpoint")]
-#[post("/{id}/deploy")]
-pub async fn add(
-    user: web::ReqData<Arc<models::User>>,
-    path: web::Path<(i32,)>,
-    pg_pool: Data<PgPool>,
-    mq_manager: Data<MqManager>,
-    sets: Data<Settings>,
-) -> Result<impl Responder> {
-    let id = path.0;
-    let stack = db::stack::fetch(pg_pool.get_ref(), id)
-        .await
-        .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(err))
-        .and_then(|stack| match stack {
-            Some(stack) => Ok(stack),
-            None => Err(JsonResponse::<models::Stack>::build().not_found("not found")),
-        })?;
-
-    let id = stack.id.clone();
-    let dc = DcBuilder::new(stack);
-    let fc = dc.build().map_err(|err| {
-        JsonResponse::<models::Stack>::build().internal_server_error(err)
-    })?;
-
-    let mut stack_data = forms::stack::Payload::try_from(&dc.stack)
-        .map_err(|err| JsonResponse::<models::Stack>::build().bad_request(err))?;
-    stack_data.user_token = Some(user.id.clone());
-    stack_data.user_email = Some(user.email.clone());
-    // let compressed = fc.unwrap_or("".to_string());
-    stack_data.docker_compose = Some(compress(fc.as_str()));
-
-    mq_manager
-        .publish_and_confirm(
-            "install".to_string(),
-            "install.start.tfa.all.all".to_string(),
-            &stack_data,
-        )
-        .await
-        .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(err))
-        .map(|_| {
-            JsonResponse::<models::Stack>::build()
-                .set_id(id)
-                .ok("Success")
-        })
-}
diff --git a/src/routes/stack/get.rs b/src/routes/stack/get.rs
deleted file mode 100644
index ccb9c9de..00000000
--- a/src/routes/stack/get.rs
+++ /dev/null
@@ -1,48 +0,0 @@
-use crate::db;
-use crate::helpers::JsonResponse;
-use crate::models;
-use actix_web::{get, web, Responder, Result};
-use sqlx::PgPool;
-use std::convert::From;
-use std::sync::Arc;
-use tracing::Instrument;
-
-#[tracing::instrument(name = "Get logged user stack.")]
-#[get("/{id}")]
-pub async fn item(
-    user: web::ReqData<Arc<models::User>>,
-    path: web::Path<(i32,)>,
-    pg_pool: web::Data<PgPool>,
-) -> Result<impl Responder> {
-    /// Get stack apps of logged user only
-    let (id,) = path.into_inner();
-
-    db::stack::fetch(pg_pool.get_ref(), id)
-        .await
-        .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(err))
-        .and_then(|stack| match stack {
-            Some(stack) if stack.user_id != user.id => {
-                Err(JsonResponse::<models::Stack>::build().not_found("not found"))
-            }
-            Some(stack) => Ok(JsonResponse::build().set_item(Some(stack)).ok("OK")),
-            None => Err(JsonResponse::<models::Stack>::build().not_found("not found")),
-        })
-}
-
-#[tracing::instrument(name = "Get user's stack list.")]
-#[get("/user/{id}")]
-pub async fn list(
-    user: web::ReqData<Arc<models::User>>,
-    path: web::Path<(String,)>,
-    pg_pool: web::Data<PgPool>,
-) -> Result<impl Responder> {
-    /// This is admin endpoint, used by a client app, client app is confidential
-    /// it should return stacks by user id
-    /// in order to pass validation at external deployment service
-    let user_id = path.into_inner().0;
-
-    db::stack::fetch_by_user(pg_pool.get_ref(), &user_id)
-        .await
-        .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(err))
-        .map(|stacks| JsonResponse::build().set_list(stacks).ok("OK"))
-}
diff --git a/src/routes/stack/update.rs b/src/routes/stack/update.rs
deleted file mode 100644
index ef11d825..00000000
--- a/src/routes/stack/update.rs
+++ /dev/null
@@ -1,65 +0,0 @@
-use crate::forms;
-use crate::helpers::JsonResponse;
-use crate::models;
-use crate::db;
-use actix_web::{web, web::Data, Responder, Result, post};
-use serde_json::Value;
-use serde_valid::Validate;
-use sqlx::PgPool;
-use std::sync::Arc;
-use tracing::Instrument;
-use uuid::Uuid;
-
-#[tracing::instrument(name = "Update stack.")]
-#[post("/{id}")]
-pub async fn update(
-    path: web::Path<(i32,)>,
-    form: web::Json<forms::stack::Stack>,
-    user: web::ReqData<Arc<models::User>>,
-    pg_pool: Data<PgPool>,
-) -> Result<impl Responder> {
-    let id = path.0;
-    let mut stack = db::stack::fetch(pg_pool.get_ref(), id)
-        .await
-        .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(err))
-        .and_then(|stack| match stack {
-            Some(stack) => Ok(stack),
-            None => Err(JsonResponse::<models::Stack>::build().not_found("Object not found")),
-        })?;
-
-    let stack_name = form.custom.custom_stack_code.clone();
-    tracing::debug!("form data: {:?}", form);
-    let user_id = user.id.clone();
-
-    if let Err(errors) = form.validate() {
-        return Err(JsonResponse::<models::Stack>::build().form_error(errors.to_string()));
-    }
-
-    let form_inner = form.into_inner();
-
-    if !form_inner.is_readable_docker_image().await.is_ok() {
-        return Err(JsonResponse::<models::Stack>::build().bad_request("Can not access docker image"));
-    }
-
-    let body: Value = serde_json::to_value::<forms::stack::Stack>(form_inner)
-        .map_err(|err| 
-            JsonResponse::<models::Stack>::build().bad_request(format!("{err}"))
-        )?; 
-
-    stack.stack_id = Uuid::new_v4();
-    stack.user_id = user_id;
-    stack.name = stack_name;
-    stack.body = body;
-
-    db::stack::update(pg_pool.get_ref(), stack)
-        .await
-        .map(|stack| {
-            JsonResponse::<models::Stack>::build()
-                .set_item(stack)
-                .ok("success")
-        })
-        .map_err(|err| {
-            tracing::error!("Failed to execute query: {:?}", err);
-            JsonResponse::<models::Stack>::build().internal_server_error("")
-        })
-}
diff --git a/src/services/mod.rs b/src/services/mod.rs
index f61186ff..94b4efce 100644
--- a/src/services/mod.rs
+++ b/src/services/mod.rs
@@ -1,2 +1,2 @@
-pub mod stack;
+pub mod project;
 mod rating;
\ No newline at end of file
diff --git a/src/services/project.rs b/src/services/project.rs
new file mode 100644
index 00000000..e69de29b
diff --git a/src/services/rating.rs b/src/services/rating.rs
index 82222211..837be7ba 100644
--- a/src/services/rating.rs
+++ b/src/services/rating.rs
@@ -1,6 +1,6 @@
-use crate::models::rating::Rating;
-use tracing::Instrument;
-use tracing_subscriber::fmt::format;
+// use crate::models::rating::Rating;
+// use tracing::Instrument;
+// use tracing_subscriber::fmt::format;
 
 // impl Rating {
     // pub async fn filter_by(query_string: &str, pool: PgPool) -> Result<()> {
diff --git a/src/startup.rs b/src/startup.rs
index 35481927..e489cb95 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -66,18 +66,38 @@ pub async fn run(
                     .service(crate::routes::rating::list_handler),
             )
             .service(
-                web::scope("/stack")
+                web::scope("/project")
                     .wrap(HttpAuthentication::bearer(
                         middleware::trydirect::bearer_guard,
                     ))
                     .wrap(Cors::permissive())
-                    .service(crate::routes::stack::deploy::add)
-                    .service(crate::routes::stack::compose::add)
-                    .service(crate::routes::stack::compose::admin)
-                    .service(crate::routes::stack::get::item)
-                    .service(crate::routes::stack::get::list)
-                    .service(crate::routes::stack::add::add)
-                    .service(crate::routes::stack::update::update),
+                    .service(crate::routes::project::deploy::add)
+                    .service(crate::routes::project::compose::add)
+                    .service(crate::routes::project::compose::admin)
+                    .service(crate::routes::project::get::item)
+                    .service(crate::routes::project::get::list)
+                    .service(crate::routes::project::add::add)
+                    .service(crate::routes::project::update::update),
+            )
+            .service(
+                web::scope("/cloud")
+                    .wrap(HttpAuthentication::bearer(
+                        middleware::trydirect::bearer_guard,
+                    ))
+                    .wrap(Cors::permissive())
+                    .service(crate::routes::cloud::get::item)
+                    .service(crate::routes::cloud::get::list)
+                    .service(crate::routes::cloud::add::add)
+            )
+            .service(
+                web::scope("/server")
+                    .wrap(HttpAuthentication::bearer(
+                        middleware::trydirect::bearer_guard,
+                    ))
+                    .wrap(Cors::permissive())
+                    .service(crate::routes::server::get::item)
+                    .service(crate::routes::server::get::list)
+                    .service(crate::routes::server::add::add)
             )
             .app_data(pg_pool.clone())
             .app_data(mq_manager.clone())
diff --git a/tests/dockerhub.rs b/tests/dockerhub.rs
index 200b665d..4134309e 100644
--- a/tests/dockerhub.rs
+++ b/tests/dockerhub.rs
@@ -2,8 +2,8 @@
 // use std::collections::HashMap;
 use std::env;
 mod common;
-use stacker::forms::stack::DockerImage;
-use stacker::helpers::stack::dctypes::{ComposeVolume, SingleValue};
+use stacker::forms::project::DockerImage;
+use stacker::helpers::project::dctypes::{ComposeVolume, SingleValue};
 use serde_yaml;
 use stacker::forms::Volume;
 
@@ -12,7 +12,7 @@ const DOCKER_PASSWORD: &str = "***************";
 //  Unit Test
 
 // #[test]
-// fn test_deserialize_user_stack_web() {
+// fn test_deserialize_project_web() {
 //
 //     let body_str = fs::read_to_string("./tests/web-item.json").unwrap();
 //     // let form:serde_json::Value = serde_json::from_str(&body_str).unwrap();
@@ -24,17 +24,17 @@ const DOCKER_PASSWORD: &str = "***************";
 //     //     }
 //     //     Err(_err) => {
 //     //         let msg = format!("Invalid data. {:?}", _err);
-//     //         return JsonResponse::<StackForm>::build().bad_request(msg);
+//     //         return JsonResponse::<ProjectForm>::build().bad_request(msg);
 //     //     }
 //     // };
 //     //
 //     // assert_eq!(result, 12);
 // }
 // #[test]
-// fn test_deserialize_user_stack() {
+// fn test_deserialize_project() {
 //
-//     let body_str = fs::read_to_string("./tests/custom-stack-payload-11.json").unwrap();
-//     let form = serde_json::from_str::<StackForm>(&body_str).unwrap();
+//     let body_str = fs::read_to_string("./tests/custom-project-payload-11.json").unwrap();
+//     let form = serde_json::from_str::<ProjectForm>(&body_str).unwrap();
 //     println!("{:?}", form);
 //     // @todo assert required data
 //
@@ -44,7 +44,7 @@ const DOCKER_PASSWORD: &str = "***************";
 //     //     }
 //     //     Err(_err) => {
 //     //         let msg = format!("Invalid data. {:?}", _err);
-//     //         return JsonResponse::<StackForm>::build().bad_request(msg);
+//     //         return JsonResponse::<ProjectForm>::build().bad_request(msg);
 //     //     }
 //     // };
 //     //
@@ -122,6 +122,6 @@ async fn test_docker_named_volume() {
     println!("ComposeVolume: {:?}", cv);
     println!("{:?}", cv.driver_opts);
     assert_eq!(Some("flask-data".to_string()), cv.name);
-    assert_eq!(&Some(SingleValue::String("/root/stack/flask-data".to_string())), cv.driver_opts.get("device").unwrap());
+    assert_eq!(&Some(SingleValue::String("/root/project/flask-data".to_string())), cv.driver_opts.get("device").unwrap());
     assert_eq!(&Some(SingleValue::String("none".to_string())), cv.driver_opts.get("type").unwrap());
 }
diff --git a/tests/model_user_stack.rs b/tests/model_user_stack.rs
index 88a005b5..e5fd40da 100644
--- a/tests/model_user_stack.rs
+++ b/tests/model_user_stack.rs
@@ -1,13 +1,13 @@
-use stacker::forms::stack::StackForm;
-use stacker::forms::stack::DockerImage;
-use stacker::forms::stack::App;
+use stacker::forms::project::ProjectForm;
+use stacker::forms::project::DockerImage;
+use stacker::forms::project::App;
 use std::fs;
 use std::collections::HashMap;
 
 //  Unit Test
 
 // #[test]
-// fn test_deserialize_user_stack_web() {
+// fn test_deserialize_project_web() {
 //
 //     let body_str = fs::read_to_string("./tests/web-item.json").unwrap();
 //     // let form:serde_json::Value = serde_json::from_str(&body_str).unwrap();
@@ -19,17 +19,17 @@ use std::collections::HashMap;
 //     //     }
 //     //     Err(_err) => {
 //     //         let msg = format!("Invalid data. {:?}", _err);
-//     //         return JsonResponse::<StackForm>::build().bad_request(msg);
+//     //         return JsonResponse::<ProjectForm>::build().bad_request(msg);
 //     //     }
 //     // };
 //     //
 //     // assert_eq!(result, 12);
 // }
 #[test]
-fn test_deserialize_user_stack() {
+fn test_deserialize_project() {
 
-    let body_str = fs::read_to_string("./tests/custom-stack-payload-11.json").unwrap();
-    let form = serde_json::from_str::<StackForm>(&body_str).unwrap();
+    let body_str = fs::read_to_string("./tests/custom-project-payload-11.json").unwrap();
+    let form = serde_json::from_str::<ProjectForm>(&body_str).unwrap();
     println!("{:?}", form);
     // @todo assert required data
 
@@ -39,7 +39,7 @@ fn test_deserialize_user_stack() {
     //     }
     //     Err(_err) => {
     //         let msg = format!("Invalid data. {:?}", _err);
-    //         return JsonResponse::<StackForm>::build().bad_request(msg);
+    //         return JsonResponse::<ProjectForm>::build().bad_request(msg);
     //     }
     // };
     //

From 0b5f0507e37e62434a6d230d5a17b1b2644bd562 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Thu, 29 Feb 2024 15:43:13 +0200
Subject: [PATCH 194/284] fix Dockerfile, add casbin config, delete dctypes
 files use requirement

---
 Dockerfile                                    |   1 +
 docker/local/configuration.yaml               |   2 +
 .../project/dctypes/advanced_build_step.rs    |  23 --
 .../dctypes/advanced_network_settings.rs      |  12 -
 .../project/dctypes/advanced_networks.rs      |  13 -
 src/helpers/project/dctypes/build_args.rs     |  16 -
 src/helpers/project/dctypes/build_step.rs     |   9 -
 src/helpers/project/dctypes/compose.rs        |  25 --
 src/helpers/project/dctypes/compose_file.rs   |  18 -
 .../project/dctypes/compose_network.rs        |   9 -
 .../compose_network_setting_details.rs        |   7 -
 .../project/dctypes/compose_networks.rs       |  19 -
 src/helpers/project/dctypes/compose_volume.rs |  22 --
 .../project/dctypes/depends_condition.rs      |   6 -
 .../project/dctypes/depends_on_options.rs     |  30 --
 src/helpers/project/dctypes/env_file.rs       |   9 -
 src/helpers/project/dctypes/environment.rs    |  30 --
 src/helpers/project/dctypes/extension.rs      |  28 --
 .../project/dctypes/extension_parse_error.rs  |  15 -
 .../dctypes/external_network_setting_bool.rs  |   5 -
 .../project/dctypes/external_volume.rs        |   8 -
 src/helpers/project/dctypes/ipam.rs           |  12 -
 src/helpers/project/dctypes/ipam_config.rs    |   9 -
 src/helpers/project/dctypes/labels.rs         |  31 --
 .../project/dctypes/logging_parameters.rs     |  15 -
 src/helpers/project/dctypes/mod.rs            | 326 ------------------
 .../project/dctypes/network_settings.rs       |  29 --
 src/helpers/project/dctypes/networks.rs       |  24 --
 src/helpers/project/dctypes/port.rs           |  53 ---
 src/helpers/project/dctypes/ports.rs          |  25 --
 src/helpers/project/dctypes/published_port.rs |   8 -
 src/helpers/project/dctypes/service.rs        | 120 -------
 src/helpers/project/dctypes/services.rs       |  19 -
 src/helpers/project/dctypes/single_service.rs |   8 -
 src/helpers/project/dctypes/sys_ctls.rs       |  30 --
 src/helpers/project/dctypes/tmpfs.rs          |   9 -
 .../project/dctypes/top_level_volumes.rs      |  19 -
 src/helpers/project/dctypes/ulimit.rs         |   8 -
 src/helpers/project/dctypes/ulimits.rs        |  20 --
 .../{model_user_stack.rs => model_project.rs} |   0
 40 files changed, 3 insertions(+), 1069 deletions(-)
 delete mode 100644 src/helpers/project/dctypes/advanced_build_step.rs
 delete mode 100644 src/helpers/project/dctypes/advanced_network_settings.rs
 delete mode 100644 src/helpers/project/dctypes/advanced_networks.rs
 delete mode 100644 src/helpers/project/dctypes/build_args.rs
 delete mode 100644 src/helpers/project/dctypes/build_step.rs
 delete mode 100644 src/helpers/project/dctypes/compose.rs
 delete mode 100644 src/helpers/project/dctypes/compose_file.rs
 delete mode 100644 src/helpers/project/dctypes/compose_network.rs
 delete mode 100644 src/helpers/project/dctypes/compose_network_setting_details.rs
 delete mode 100644 src/helpers/project/dctypes/compose_networks.rs
 delete mode 100644 src/helpers/project/dctypes/compose_volume.rs
 delete mode 100644 src/helpers/project/dctypes/depends_condition.rs
 delete mode 100644 src/helpers/project/dctypes/depends_on_options.rs
 delete mode 100644 src/helpers/project/dctypes/env_file.rs
 delete mode 100644 src/helpers/project/dctypes/environment.rs
 delete mode 100644 src/helpers/project/dctypes/extension.rs
 delete mode 100644 src/helpers/project/dctypes/extension_parse_error.rs
 delete mode 100644 src/helpers/project/dctypes/external_network_setting_bool.rs
 delete mode 100644 src/helpers/project/dctypes/external_volume.rs
 delete mode 100644 src/helpers/project/dctypes/ipam.rs
 delete mode 100644 src/helpers/project/dctypes/ipam_config.rs
 delete mode 100644 src/helpers/project/dctypes/labels.rs
 delete mode 100644 src/helpers/project/dctypes/logging_parameters.rs
 delete mode 100644 src/helpers/project/dctypes/mod.rs
 delete mode 100644 src/helpers/project/dctypes/network_settings.rs
 delete mode 100644 src/helpers/project/dctypes/networks.rs
 delete mode 100644 src/helpers/project/dctypes/port.rs
 delete mode 100644 src/helpers/project/dctypes/ports.rs
 delete mode 100644 src/helpers/project/dctypes/published_port.rs
 delete mode 100644 src/helpers/project/dctypes/service.rs
 delete mode 100644 src/helpers/project/dctypes/services.rs
 delete mode 100644 src/helpers/project/dctypes/single_service.rs
 delete mode 100644 src/helpers/project/dctypes/sys_ctls.rs
 delete mode 100644 src/helpers/project/dctypes/tmpfs.rs
 delete mode 100644 src/helpers/project/dctypes/top_level_volumes.rs
 delete mode 100644 src/helpers/project/dctypes/ulimit.rs
 delete mode 100644 src/helpers/project/dctypes/ulimits.rs
 rename tests/{model_user_stack.rs => model_project.rs} (100%)

diff --git a/Dockerfile b/Dockerfile
index c068fc29..34fa4b03 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -54,6 +54,7 @@ COPY --from=builder /app/target/release/server .
 COPY --from=builder /app/.env .
 COPY --from=builder /app/configuration.yaml .
 COPY --from=builder /usr/local/cargo/bin/sqlx sqlx
+COPY ./access_control.conf.dist /app
 
 EXPOSE 8000
 
diff --git a/docker/local/configuration.yaml b/docker/local/configuration.yaml
index a0bb4c00..750f1cbb 100644
--- a/docker/local/configuration.yaml
+++ b/docker/local/configuration.yaml
@@ -1,6 +1,8 @@
 app_host: 0.0.0.0
 app_port: 8000
 auth_url: https://dev.try.direct/server/user/oauth_server/api/me
+max_clients_number: 2
+
 database:
   host: 172.17.0.2
   port: 5432
diff --git a/src/helpers/project/dctypes/advanced_build_step.rs b/src/helpers/project/dctypes/advanced_build_step.rs
deleted file mode 100644
index 41f2f0c0..00000000
--- a/src/helpers/project/dctypes/advanced_build_step.rs
+++ /dev/null
@@ -1,23 +0,0 @@
-use serde::{Deserialize, Serialize};
-use crate::helpers::project::dctypes::*;
-
-#[derive(Builder, Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Default)]
-#[serde(deny_unknown_fields)]
-#[builder(setter(into), default)]
-pub struct AdvancedBuildStep {
-    pub context: String,
-    #[serde(default, skip_serializing_if = "Option::is_none")]
-    pub dockerfile: Option<String>,
-    #[serde(default, skip_serializing_if = "Option::is_none")]
-    pub args: Option<BuildArgs>,
-    #[serde(default, skip_serializing_if = "Option::is_none")]
-    pub shm_size: Option<u64>,
-    #[serde(default, skip_serializing_if = "Option::is_none")]
-    pub target: Option<String>,
-    #[serde(default, skip_serializing_if = "Option::is_none")]
-    pub network: Option<String>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub cache_from: Vec<String>,
-    #[serde(default, skip_serializing_if = "Labels::is_empty")]
-    pub labels: Labels,
-}
diff --git a/src/helpers/project/dctypes/advanced_network_settings.rs b/src/helpers/project/dctypes/advanced_network_settings.rs
deleted file mode 100644
index 730c9cb5..00000000
--- a/src/helpers/project/dctypes/advanced_network_settings.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-use serde::{Deserialize, Serialize};
-
-#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(deny_unknown_fields)]
-pub struct AdvancedNetworkSettings {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub ipv4_address: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub ipv6_address: Option<String>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub aliases: Vec<String>,
-}
diff --git a/src/helpers/project/dctypes/advanced_networks.rs b/src/helpers/project/dctypes/advanced_networks.rs
deleted file mode 100644
index 2b4f0b0a..00000000
--- a/src/helpers/project/dctypes/advanced_networks.rs
+++ /dev/null
@@ -1,13 +0,0 @@
-use serde::{Deserialize, Serialize};
-#[cfg(feature = "indexmap")]
-use indexmap::IndexMap;
-#[cfg(not(feature = "indexmap"))]
-use std::collections::HashMap;
-use crate::helpers::project::dctypes::*;
-
-#[cfg(feature = "indexmap")]
-#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
-pub struct AdvancedNetworks(pub IndexMap<String, MapOrEmpty<AdvancedNetworkSettings>>);
-#[cfg(not(feature = "indexmap"))]
-#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
-pub struct AdvancedNetworks(pub HashMap<String, MapOrEmpty<AdvancedNetworkSettings>>);
diff --git a/src/helpers/project/dctypes/build_args.rs b/src/helpers/project/dctypes/build_args.rs
deleted file mode 100644
index 8f629662..00000000
--- a/src/helpers/project/dctypes/build_args.rs
+++ /dev/null
@@ -1,16 +0,0 @@
-use serde::{Deserialize, Serialize};
-#[cfg(feature = "indexmap")]
-use indexmap::IndexMap;
-#[cfg(not(feature = "indexmap"))]
-use std::collections::HashMap;
-
-#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
-#[serde(untagged)]
-pub enum BuildArgs {
-    Simple(String),
-    List(Vec<String>),
-    #[cfg(feature = "indexmap")]
-    KvPair(IndexMap<String, String>),
-    #[cfg(not(feature = "indexmap"))]
-    KvPair(HashMap<String, String>),
-}
diff --git a/src/helpers/project/dctypes/build_step.rs b/src/helpers/project/dctypes/build_step.rs
deleted file mode 100644
index 3c1fc49f..00000000
--- a/src/helpers/project/dctypes/build_step.rs
+++ /dev/null
@@ -1,9 +0,0 @@
-use serde::{Deserialize, Serialize};
-use crate::helpers::project::dctypes;
-
-#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
-#[serde(untagged)]
-pub enum BuildStep {
-    Simple(String),
-    Advanced(dctypes::AdvancedBuildStep),
-}
diff --git a/src/helpers/project/dctypes/compose.rs b/src/helpers/project/dctypes/compose.rs
deleted file mode 100644
index 4bee50a0..00000000
--- a/src/helpers/project/dctypes/compose.rs
+++ /dev/null
@@ -1,25 +0,0 @@
-use serde::{Deserialize, Serialize};
-use crate::helpers::project::dctypes::*;
-#[cfg(feature = "indexmap")]
-use indexmap::IndexMap;
-use serde_yaml::Value;
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
-pub struct Compose {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub version: Option<String>,
-    #[serde(default, skip_serializing_if = "Services::is_empty")]
-    pub services: Services,
-    #[serde(default, skip_serializing_if = "TopLevelVolumes::is_empty")]
-    pub volumes: TopLevelVolumes,
-    #[serde(default, skip_serializing_if = "ComposeNetworks::is_empty")]
-    pub networks: ComposeNetworks,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub service: Option<Service>,
-    #[cfg(feature = "indexmap")]
-    #[serde(flatten, skip_serializing_if = "IndexMap::is_empty")]
-    pub extensions: IndexMap<Extension, Value>,
-    #[cfg(not(feature = "indexmap"))]
-    #[serde(flatten, skip_serializing_if = "HashMap::is_empty")]
-    pub extensions: HashMap<Extension, Value>,
-}
diff --git a/src/helpers/project/dctypes/compose_file.rs b/src/helpers/project/dctypes/compose_file.rs
deleted file mode 100644
index 79abe918..00000000
--- a/src/helpers/project/dctypes/compose_file.rs
+++ /dev/null
@@ -1,18 +0,0 @@
-use serde::{Deserialize, Serialize};
-use crate::helpers::project::dctypes;
-
-#[cfg(feature = "indexmap")]
-use indexmap::IndexMap;
-
-#[allow(clippy::large_enum_variant)]
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
-#[serde(untagged)]
-pub enum ComposeFile {
-    V2Plus(dctypes::Compose),
-    #[cfg(feature = "indexmap")]
-    V1(IndexMap<String, dctypes::Service>),
-    #[cfg(not(feature = "indexmap"))]
-    V1(HashMap<String, Service>),
-    Single(dctypes::SingleService),
-}
-
diff --git a/src/helpers/project/dctypes/compose_network.rs b/src/helpers/project/dctypes/compose_network.rs
deleted file mode 100644
index 56d3de15..00000000
--- a/src/helpers/project/dctypes/compose_network.rs
+++ /dev/null
@@ -1,9 +0,0 @@
-use serde::{Deserialize, Serialize};
-use crate::helpers::project::dctypes::*;
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(untagged)]
-pub enum ComposeNetwork {
-    Detailed(ComposeNetworkSettingDetails),
-    Bool(bool),
-}
diff --git a/src/helpers/project/dctypes/compose_network_setting_details.rs b/src/helpers/project/dctypes/compose_network_setting_details.rs
deleted file mode 100644
index 59d0348c..00000000
--- a/src/helpers/project/dctypes/compose_network_setting_details.rs
+++ /dev/null
@@ -1,7 +0,0 @@
-use serde::{Deserialize, Serialize};
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(deny_unknown_fields)]
-pub struct ComposeNetworkSettingDetails {
-    pub name: String,
-}
diff --git a/src/helpers/project/dctypes/compose_networks.rs b/src/helpers/project/dctypes/compose_networks.rs
deleted file mode 100644
index da6aae11..00000000
--- a/src/helpers/project/dctypes/compose_networks.rs
+++ /dev/null
@@ -1,19 +0,0 @@
-#[cfg(feature = "indexmap")]
-use indexmap::IndexMap;
-#[cfg(not(feature = "indexmap"))]
-use std::collections::HashMap;
-use serde::{Deserialize, Serialize};
-use crate::helpers::project::dctypes::*;
-
-#[cfg(feature = "indexmap")]
-#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
-pub struct ComposeNetworks(pub IndexMap<String, MapOrEmpty<NetworkSettings>>);
-#[cfg(not(feature = "indexmap"))]
-#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
-pub struct ComposeNetworks(pub HashMap<String, MapOrEmpty<NetworkSettings>>);
-
-impl ComposeNetworks {
-    pub fn is_empty(&self) -> bool {
-        self.0.is_empty()
-    }
-}
diff --git a/src/helpers/project/dctypes/compose_volume.rs b/src/helpers/project/dctypes/compose_volume.rs
deleted file mode 100644
index 644b67cf..00000000
--- a/src/helpers/project/dctypes/compose_volume.rs
+++ /dev/null
@@ -1,22 +0,0 @@
-use serde::{Deserialize, Serialize};
-use crate::helpers::project::dctypes::*;
-#[cfg(feature = "indexmap")]
-use indexmap::IndexMap;
-#[cfg(not(feature = "indexmap"))]
-use std::collections::HashMap;
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
-pub struct ComposeVolume {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub driver: Option<String>,
-    #[cfg(feature = "indexmap")]
-    #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
-    pub driver_opts: IndexMap<String, Option<SingleValue>>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub external: Option<ExternalVolume>,
-    #[serde(default, skip_serializing_if = "Labels::is_empty")]
-    pub labels: Labels,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub name: Option<String>,
-}
-
diff --git a/src/helpers/project/dctypes/depends_condition.rs b/src/helpers/project/dctypes/depends_condition.rs
deleted file mode 100644
index 863c7b5c..00000000
--- a/src/helpers/project/dctypes/depends_condition.rs
+++ /dev/null
@@ -1,6 +0,0 @@
-use serde::{Deserialize, Serialize};
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-pub struct DependsCondition {
-    pub condition: String,
-}
diff --git a/src/helpers/project/dctypes/depends_on_options.rs b/src/helpers/project/dctypes/depends_on_options.rs
deleted file mode 100644
index fbf79119..00000000
--- a/src/helpers/project/dctypes/depends_on_options.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-use serde::{Deserialize, Serialize};
-#[cfg(feature = "indexmap")]
-use indexmap::IndexMap;
-use crate::helpers::project::dctypes;
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
-#[serde(untagged)]
-pub enum DependsOnOptions {
-    Simple(Vec<String>),
-    #[cfg(feature = "indexmap")]
-    Conditional(IndexMap<String, dctypes::DependsCondition>),
-    #[cfg(not(feature = "indexmap"))]
-    Conditional(HashMap<String, dctypes::DependsCondition>),
-}
-
-impl Default for DependsOnOptions {
-    fn default() -> Self {
-        Self::Simple(Vec::new())
-    }
-}
-
-impl DependsOnOptions {
-    pub fn is_empty(&self) -> bool {
-        match self {
-            Self::Simple(v) => v.is_empty(),
-            Self::Conditional(m) => m.is_empty(),
-        }
-    }
-}
-
diff --git a/src/helpers/project/dctypes/env_file.rs b/src/helpers/project/dctypes/env_file.rs
deleted file mode 100644
index 643d6b99..00000000
--- a/src/helpers/project/dctypes/env_file.rs
+++ /dev/null
@@ -1,9 +0,0 @@
-use serde::{Deserialize, Serialize};
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(untagged)]
-pub enum EnvFile {
-    Simple(String),
-    List(Vec<String>),
-}
-
diff --git a/src/helpers/project/dctypes/environment.rs b/src/helpers/project/dctypes/environment.rs
deleted file mode 100644
index a04f9920..00000000
--- a/src/helpers/project/dctypes/environment.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-use serde::{Deserialize, Serialize};
-use serde_yaml::Value;
-#[cfg(feature = "indexmap")]
-use indexmap::IndexMap;
-use crate::helpers::project::dctypes;
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
-#[serde(untagged)]
-pub enum Environment {
-    List(Vec<String>),
-    #[cfg(feature = "indexmap")]
-    KvPair(IndexMap<String, Option<dctypes::SingleValue>>),
-    #[cfg(not(feature = "indexmap"))]
-    KvPair(HashMap<String, Option<dctypes::SingleValue>>),
-}
-
-impl Default for Environment {
-    fn default() -> Self {
-        Self::List(Vec::new())
-    }
-}
-
-impl Environment {
-    pub fn is_empty(&self) -> bool {
-        match self {
-            Self::List(v) => v.is_empty(),
-            Self::KvPair(m) => m.is_empty(),
-        }
-    }
-}
diff --git a/src/helpers/project/dctypes/extension.rs b/src/helpers/project/dctypes/extension.rs
deleted file mode 100644
index 1c0b412d..00000000
--- a/src/helpers/project/dctypes/extension.rs
+++ /dev/null
@@ -1,28 +0,0 @@
-use serde::{Deserialize, Serialize};
-use crate::helpers::project::dctypes;
-use std::str::FromStr;
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default, Ord, PartialOrd)]
-#[serde(try_from = "String")]
-pub struct Extension(String);
-
-impl FromStr for Extension {
-    type Err = dctypes::ExtensionParseError;
-
-    fn from_str(s: &str) -> Result<Self, Self::Err> {
-        let owned = s.to_owned();
-        Extension::try_from(owned)
-    }
-}
-
-impl TryFrom<String> for Extension {
-    type Error = dctypes::ExtensionParseError;
-
-    fn try_from(s: String) -> Result<Self, Self::Error> {
-        if s.starts_with("x-") {
-            Ok(Self(s))
-        } else {
-            Err(dctypes::ExtensionParseError(s))
-        }
-    }
-}
diff --git a/src/helpers/project/dctypes/extension_parse_error.rs b/src/helpers/project/dctypes/extension_parse_error.rs
deleted file mode 100644
index 9fcec518..00000000
--- a/src/helpers/project/dctypes/extension_parse_error.rs
+++ /dev/null
@@ -1,15 +0,0 @@
-use std::fmt;
-
-/// The result of a failed TryFrom<String> conversion for [`Extension`]
-///
-/// Contains the string that was being converted
-#[derive(Clone, Debug, Eq, PartialEq, Hash)]
-pub struct ExtensionParseError(pub String);
-
-impl fmt::Display for ExtensionParseError {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "unknown attribute {:?}, extensions must start with 'x-' (see https://docs.docker.com/compose/compose-file/#extension)", self.0)
-    }
-}
-
-impl std::error::Error for ExtensionParseError {}
diff --git a/src/helpers/project/dctypes/external_network_setting_bool.rs b/src/helpers/project/dctypes/external_network_setting_bool.rs
deleted file mode 100644
index 1d1ad3d3..00000000
--- a/src/helpers/project/dctypes/external_network_setting_bool.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-use serde::{Deserialize, Serialize};
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(deny_unknown_fields)]
-pub struct ExternalNetworkSettingBool(bool);
diff --git a/src/helpers/project/dctypes/external_volume.rs b/src/helpers/project/dctypes/external_volume.rs
deleted file mode 100644
index ba03e26b..00000000
--- a/src/helpers/project/dctypes/external_volume.rs
+++ /dev/null
@@ -1,8 +0,0 @@
-use serde::{Deserialize, Serialize};
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
-#[serde(untagged)]
-pub enum ExternalVolume {
-    Bool(bool),
-    Name { name: String },
-}
diff --git a/src/helpers/project/dctypes/ipam.rs b/src/helpers/project/dctypes/ipam.rs
deleted file mode 100644
index 99e8b1f1..00000000
--- a/src/helpers/project/dctypes/ipam.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-use serde::{Deserialize, Serialize};
-use crate::helpers::project::dctypes::*;
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(deny_unknown_fields)]
-pub struct Ipam {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub driver: Option<String>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub config: Vec<IpamConfig>,
-}
-
diff --git a/src/helpers/project/dctypes/ipam_config.rs b/src/helpers/project/dctypes/ipam_config.rs
deleted file mode 100644
index f40c2f59..00000000
--- a/src/helpers/project/dctypes/ipam_config.rs
+++ /dev/null
@@ -1,9 +0,0 @@
-use serde::{Deserialize, Serialize};
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(deny_unknown_fields)]
-pub struct IpamConfig {
-    pub subnet: String,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub gateway: Option<String>,
-}
diff --git a/src/helpers/project/dctypes/labels.rs b/src/helpers/project/dctypes/labels.rs
deleted file mode 100644
index e4296afa..00000000
--- a/src/helpers/project/dctypes/labels.rs
+++ /dev/null
@@ -1,31 +0,0 @@
-use serde::{Deserialize, Serialize};
-#[cfg(feature = "indexmap")]
-use indexmap::IndexMap;
-#[cfg(not(feature = "indexmap"))]
-use std::collections::HashMap;
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
-#[serde(untagged)]
-pub enum Labels {
-    List(Vec<String>),
-    #[cfg(feature = "indexmap")]
-    Map(IndexMap<String, String>),
-    #[cfg(not(feature = "indexmap"))]
-    Map(HashMap<String, String>),
-}
-
-impl Default for Labels {
-    fn default() -> Self {
-        Self::List(Vec::new())
-    }
-}
-
-impl Labels {
-    pub fn is_empty(&self) -> bool {
-        match self {
-            Self::List(v) => v.is_empty(),
-            Self::Map(m) => m.is_empty(),
-        }
-    }
-}
-
diff --git a/src/helpers/project/dctypes/logging_parameters.rs b/src/helpers/project/dctypes/logging_parameters.rs
deleted file mode 100644
index a0068c7b..00000000
--- a/src/helpers/project/dctypes/logging_parameters.rs
+++ /dev/null
@@ -1,15 +0,0 @@
-use serde::{Deserialize, Serialize};
-#[cfg(feature = "indexmap")]
-use indexmap::IndexMap;
-use crate::helpers::project::dctypes;
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
-pub struct LoggingParameters {
-    pub driver: String,
-    #[cfg(feature = "indexmap")]
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub options: Option<IndexMap<String, dctypes::SingleValue>>,
-    #[cfg(not(feature = "indexmap"))]
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub options: Option<HashMap<String, dctypes::SingleValue>>,
-}
diff --git a/src/helpers/project/dctypes/mod.rs b/src/helpers/project/dctypes/mod.rs
deleted file mode 100644
index 093c7f17..00000000
--- a/src/helpers/project/dctypes/mod.rs
+++ /dev/null
@@ -1,326 +0,0 @@
-mod port;
-mod published_port;
-mod compose_file;
-mod single_service;
-mod service;
-mod sys_ctls;
-mod compose;
-mod env_file;
-mod depends_on_options;
-mod depends_condition;
-mod logging_parameters;
-mod ports;
-mod environment;
-mod extension;
-mod extension_parse_error;
-mod services;
-mod labels;
-mod tmpfs;
-mod ulimit;
-mod ulimits;
-mod networks;
-mod build_step;
-mod advanced_build_step;
-mod build_args;
-mod advanced_networks;
-mod advanced_network_settings;
-mod top_level_volumes;
-mod compose_volume;
-mod external_volume;
-mod compose_network;
-mod compose_networks;
-mod compose_network_setting_details;
-mod external_network_setting_bool;
-mod network_settings;
-mod ipam;
-mod ipam_config;
-
-pub use port::*;
-pub use published_port::*;
-pub use compose_file::*;
-pub use single_service::*;
-pub use service::*;
-pub use sys_ctls::*;
-pub use compose::*;
-pub use env_file::*;
-pub use depends_on_options::*;
-pub use depends_condition::*;
-pub use logging_parameters::*;
-pub use ports::*;
-pub use environment::*;
-pub use extension::*;
-pub use extension_parse_error::*;
-pub use services::*;
-pub use labels::*;
-pub use tmpfs::*;
-pub use ulimit::*;
-pub use ulimits::*;
-pub use networks::*;
-pub use build_step::*;
-pub use advanced_build_step::*;
-pub use build_args::*;
-pub use advanced_networks::*;
-pub use advanced_network_settings::*;
-pub use top_level_volumes::*;
-pub use compose_volume::*;
-pub use external_volume::*;
-pub use compose_networks::*;
-pub use compose_network::*;
-pub use compose_network_setting_details::*;
-pub use external_network_setting_bool::*;
-pub use network_settings::*;
-pub use ipam::*;
-pub use ipam_config::*;
-
-use crate::helpers::project::dctypes;
-
-use derive_builder::*;
-#[cfg(feature = "indexmap")]
-use indexmap::IndexMap;
-use serde::{Deserialize, Serialize};
-use serde_yaml::Value;
-#[cfg(not(feature = "indexmap"))]
-use std::collections::HashMap;
-use std::convert::TryFrom;
-use std::fmt;
-
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
-#[serde(deny_unknown_fields)]
-pub struct Deploy {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub mode: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub replicas: Option<i64>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub labels: Vec<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub update_config: Option<UpdateConfig>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub resources: Option<Resources>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub restart_policy: Option<RestartPolicy>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub placement: Option<Placement>,
-}
-
-fn is_zero(val: &i64) -> bool {
-    *val == 0
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(deny_unknown_fields)]
-pub struct Healthcheck {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub test: Option<HealthcheckTest>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub interval: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub timeout: Option<String>,
-    #[serde(default, skip_serializing_if = "is_zero")]
-    pub retries: i64,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub start_period: Option<String>,
-    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
-    pub disable: bool,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(untagged)]
-pub enum HealthcheckTest {
-    Single(String),
-    Multiple(Vec<String>),
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
-#[serde(deny_unknown_fields)]
-pub struct Limits {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub cpus: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub memory: Option<String>,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
-#[serde(deny_unknown_fields)]
-pub struct Placement {
-    #[serde(skip_serializing_if = "Vec::is_empty", default)]
-    pub constraints: Vec<String>,
-    #[serde(skip_serializing_if = "Vec::is_empty", default)]
-    pub preferences: Vec<Preferences>,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(deny_unknown_fields)]
-pub struct Preferences {
-    pub spread: String,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
-#[serde(deny_unknown_fields)]
-pub struct Resources {
-    pub limits: Option<Limits>,
-    pub reservations: Option<Limits>,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
-#[serde(deny_unknown_fields)]
-pub struct RestartPolicy {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub condition: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub delay: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub max_attempts: Option<i64>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub window: Option<String>,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
-#[serde(deny_unknown_fields)]
-pub struct UpdateConfig {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub parallelism: Option<i64>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub delay: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub failure_action: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub monitor: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub max_failure_ratio: Option<f64>,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(untagged)]
-pub enum Volumes {
-    Simple(Vec<String>),
-    Advanced(Vec<AdvancedVolumes>),
-}
-
-impl Default for Volumes {
-    fn default() -> Self {
-        Self::Simple(Vec::new())
-    }
-}
-
-impl Volumes {
-    pub fn is_empty(&self) -> bool {
-        match self {
-            Self::Simple(v) => v.is_empty(),
-            Self::Advanced(v) => v.is_empty(),
-        }
-    }
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(deny_unknown_fields)]
-pub struct AdvancedVolumes {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub source: Option<String>,
-    pub target: String,
-    #[serde(rename = "type")]
-    pub _type: String,
-    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
-    pub read_only: bool,
-    #[serde(default, skip_serializing_if = "Option::is_none")]
-    pub bind: Option<Bind>,
-    #[serde(default, skip_serializing_if = "Option::is_none")]
-    pub volume: Option<Volume>,
-    #[serde(default, skip_serializing_if = "Option::is_none")]
-    pub tmpfs: Option<TmpfsSettings>,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
-#[serde(deny_unknown_fields)]
-pub struct Bind {
-    pub propagation: String,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
-#[serde(deny_unknown_fields)]
-pub struct Volume {
-    pub nocopy: bool,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
-#[serde(deny_unknown_fields)]
-pub struct TmpfsSettings {
-    pub size: u64,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(untagged)]
-pub enum Command {
-    Simple(String),
-    Args(Vec<String>),
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
-#[serde(untagged)]
-pub enum Entrypoint {
-    Simple(String),
-    List(Vec<String>),
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, PartialOrd)]
-#[serde(untagged)]
-pub enum SingleValue {
-    String(String),
-    Bool(bool),
-    Unsigned(u64),
-    Signed(i64),
-    Float(f64),
-}
-
-impl fmt::Display for SingleValue {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            Self::String(s) => f.write_str(s),
-            Self::Bool(b) => write!(f, "{b}"),
-            Self::Unsigned(u) => write!(f, "{u}"),
-            Self::Signed(i) => write!(f, "{i}"),
-            Self::Float(fl) => write!(f, "{fl}"),
-        }
-    }
-}
-
-#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash)]
-#[serde(untagged)]
-pub enum MapOrEmpty<T> {
-    Map(T),
-    Empty,
-}
-
-impl<T> Default for MapOrEmpty<T> {
-    fn default() -> Self {
-        Self::Empty
-    }
-}
-
-impl<T> From<MapOrEmpty<T>> for Option<T> {
-    fn from(value: MapOrEmpty<T>) -> Self {
-        match value {
-            MapOrEmpty::Map(t) => Some(t),
-            MapOrEmpty::Empty => None,
-        }
-    }
-}
-
-impl<T> Serialize for MapOrEmpty<T>
-    where
-        T: Serialize,
-{
-    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
-        where
-            S: serde::Serializer,
-    {
-        match self {
-            Self::Map(t) => t.serialize(serializer),
-            Self::Empty => {
-                use serde::ser::SerializeMap;
-                serializer.serialize_map(None)?.end()
-            }
-        }
-    }
-}
diff --git a/src/helpers/project/dctypes/network_settings.rs b/src/helpers/project/dctypes/network_settings.rs
deleted file mode 100644
index 72b27b29..00000000
--- a/src/helpers/project/dctypes/network_settings.rs
+++ /dev/null
@@ -1,29 +0,0 @@
-use serde::{Deserialize, Serialize};
-use crate::helpers::project::dctypes::*;
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
-#[serde(deny_unknown_fields)]
-pub struct NetworkSettings {
-    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
-    pub attachable: bool,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub driver: Option<String>,
-    #[cfg(feature = "indexmap")]
-    #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
-    pub driver_opts: IndexMap<String, Option<SingleValue>>,
-    #[cfg(not(feature = "indexmap"))]
-    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
-    pub driver_opts: HashMap<String, Option<SingleValue>>,
-    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
-    pub enable_ipv6: bool,
-    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
-    pub internal: bool,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub external: Option<ComposeNetwork>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub ipam: Option<Ipam>,
-    #[serde(default, skip_serializing_if = "Labels::is_empty")]
-    pub labels: Labels,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub name: Option<String>,
-}
diff --git a/src/helpers/project/dctypes/networks.rs b/src/helpers/project/dctypes/networks.rs
deleted file mode 100644
index 751554fb..00000000
--- a/src/helpers/project/dctypes/networks.rs
+++ /dev/null
@@ -1,24 +0,0 @@
-use serde::{Deserialize, Serialize};
-use crate::helpers::project::dctypes;
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
-#[serde(untagged)]
-pub enum Networks {
-    Simple(Vec<String>),
-    Advanced(dctypes::AdvancedNetworks),
-}
-
-impl Default for Networks {
-    fn default() -> Self {
-        Self::Simple(Vec::new())
-    }
-}
-
-impl Networks {
-    pub fn is_empty(&self) -> bool {
-        match self {
-            Self::Simple(n) => n.is_empty(),
-            Self::Advanced(n) => n.0.is_empty(),
-        }
-    }
-}
diff --git a/src/helpers/project/dctypes/port.rs b/src/helpers/project/dctypes/port.rs
deleted file mode 100644
index fc437ef4..00000000
--- a/src/helpers/project/dctypes/port.rs
+++ /dev/null
@@ -1,53 +0,0 @@
-use serde::{Deserialize, Serialize};
-use crate::helpers::project::dctypes;
-use crate::forms;
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
-pub struct Port {
-    pub target: u16,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub host_ip: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub published: Option<dctypes::PublishedPort>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub protocol: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub mode: Option<String>,
-}
-
-impl Default for Port {
-    fn default() -> Self {
-        Port {
-            target: 80,
-            host_ip: None,
-            published: None,
-            protocol: None,
-            mode: None,
-        }
-    }
-}
-
-impl TryInto<Port> for &forms::project::Port {
-    type Error = String;
-    fn try_into(self) -> Result<Port, Self::Error> {
-        let cp = self
-            .container_port
-            .as_ref()
-            .map_or(Ok(0u16), |s| s.parse::<u16>())
-            .map_err(|_| "Could not parse port".to_string())?;
-
-        let hp = self
-            .host_port
-            .as_ref()
-            .map_or(Ok(0u16), |s| s.parse::<u16>())
-            .map_err(|_| "Could not parse port".to_string())?;
-
-        Ok(Port {
-            target: cp,
-            host_ip: None,
-            published: Some(dctypes::PublishedPort::Single(hp)),
-            protocol: None,
-            mode: None,
-        })
-    }
-}
diff --git a/src/helpers/project/dctypes/ports.rs b/src/helpers/project/dctypes/ports.rs
deleted file mode 100644
index 3c61494e..00000000
--- a/src/helpers/project/dctypes/ports.rs
+++ /dev/null
@@ -1,25 +0,0 @@
-use serde::{Deserialize, Serialize};
-use crate::helpers::project::dctypes;
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
-#[serde(untagged)]
-pub enum Ports {
-    Short(Vec<String>),
-    Long(Vec<dctypes::Port>),
-}
-
-impl Default for Ports {
-    fn default() -> Self {
-        Self::Short(Vec::default())
-    }
-}
-
-impl Ports {
-    pub fn is_empty(&self) -> bool {
-        match self {
-            Self::Short(v) => v.is_empty(),
-            Self::Long(v) => v.is_empty(),
-        }
-    }
-}
-
diff --git a/src/helpers/project/dctypes/published_port.rs b/src/helpers/project/dctypes/published_port.rs
deleted file mode 100644
index 57a24ab3..00000000
--- a/src/helpers/project/dctypes/published_port.rs
+++ /dev/null
@@ -1,8 +0,0 @@
-use serde::{Deserialize, Serialize};
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
-#[serde(untagged)]
-pub enum PublishedPort {
-    Single(u16),
-    Range(String),
-}
diff --git a/src/helpers/project/dctypes/service.rs b/src/helpers/project/dctypes/service.rs
deleted file mode 100644
index e288632f..00000000
--- a/src/helpers/project/dctypes/service.rs
+++ /dev/null
@@ -1,120 +0,0 @@
-use serde::{Deserialize, Serialize};
-#[cfg(feature = "indexmap")]
-use indexmap::IndexMap;
-use crate::helpers::project::dctypes;
-use serde_json::Value;
-use derive_builder::*;
-
-#[derive(Builder, Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
-#[builder(setter(into), default)]
-pub struct Service {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub hostname: Option<String>,
-    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
-    pub privileged: bool,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub healthcheck: Option<dctypes::Healthcheck>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub deploy: Option<dctypes::Deploy>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub image: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub container_name: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none", rename = "build")]
-    pub build_: Option<dctypes::BuildStep>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub pid: Option<String>,
-    #[serde(default, skip_serializing_if = "dctypes::Ports::is_empty")]
-    pub ports: dctypes::Ports,
-    #[serde(default, skip_serializing_if = "dctypes::Environment::is_empty")]
-    pub environment: dctypes::Environment,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub network_mode: Option<String>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub devices: Vec<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub restart: Option<String>,
-    #[serde(default, skip_serializing_if = "dctypes::Labels::is_empty")]
-    pub labels: dctypes::Labels,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub tmpfs: Option<dctypes::Tmpfs>,
-    #[serde(default, skip_serializing_if = "dctypes::Ulimits::is_empty")]
-    pub ulimits: dctypes::Ulimits,
-    #[serde(default, skip_serializing_if = "dctypes::Volumes::is_empty")]
-    pub volumes: dctypes::Volumes,
-    #[serde(default, skip_serializing_if = "dctypes::Networks::is_empty")]
-    pub networks: dctypes::Networks,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub cap_add: Vec<String>,
-    #[serde(default, skip_serializing_if = "dctypes::DependsOnOptions::is_empty")]
-    pub depends_on: dctypes::DependsOnOptions,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub command: Option<dctypes::Command>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub entrypoint: Option<dctypes::Entrypoint>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub env_file: Option<dctypes::EnvFile>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub stop_grace_period: Option<String>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub profiles: Vec<String>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub links: Vec<String>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub dns: Vec<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub ipc: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub net: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub stop_signal: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub user: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub working_dir: Option<String>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub expose: Vec<String>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub volumes_from: Vec<String>,
-    #[cfg(feature = "indexmap")]
-    #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
-    pub extends: IndexMap<String, String>,
-    #[cfg(not(feature = "indexmap"))]
-    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
-    pub extends: HashMap<String, String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub logging: Option<dctypes::LoggingParameters>,
-    #[serde(default, skip_serializing_if = "dctypes::is_zero")]
-    pub scale: i64,
-    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
-    pub init: bool,
-    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
-    pub stdin_open: bool,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub shm_size: Option<String>,
-    #[cfg(feature = "indexmap")]
-    #[serde(flatten, skip_serializing_if = "IndexMap::is_empty")]
-    pub extensions: IndexMap<dctypes::Extension, Value>,
-    #[cfg(not(feature = "indexmap"))]
-    #[serde(flatten, skip_serializing_if = "HashMap::is_empty")]
-    pub extensions: HashMap<Extension, Value>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub extra_hosts: Vec<String>,
-    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
-    pub tty: bool,
-    #[serde(default, skip_serializing_if = "dctypes::SysCtls::is_empty")]
-    pub sysctls: dctypes::SysCtls,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub security_opt: Vec<String>,
-}
-
-impl Service {
-    pub fn image(&self) -> &str {
-        self.image.as_deref().unwrap_or_default()
-    }
-
-    pub fn network_mode(&self) -> &str {
-        self.network_mode.as_deref().unwrap_or_default()
-    }
-}
-
diff --git a/src/helpers/project/dctypes/services.rs b/src/helpers/project/dctypes/services.rs
deleted file mode 100644
index 949437e8..00000000
--- a/src/helpers/project/dctypes/services.rs
+++ /dev/null
@@ -1,19 +0,0 @@
-use serde::{Deserialize, Serialize};
-#[cfg(feature = "indexmap")]
-use indexmap::IndexMap;
-#[cfg(not(feature = "indexmap"))]
-use std::collections::HashMap;
-use crate::helpers::project::dctypes;
-
-#[cfg(feature = "indexmap")]
-#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
-pub struct Services(pub IndexMap<String, Option<dctypes::Service>>);
-#[cfg(not(feature = "indexmap"))]
-#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
-pub struct Services(pub HashMap<String, Option<dctypes::Service>>);
-
-impl Services {
-    pub fn is_empty(&self) -> bool {
-        self.0.is_empty()
-    }
-}
diff --git a/src/helpers/project/dctypes/single_service.rs b/src/helpers/project/dctypes/single_service.rs
deleted file mode 100644
index 19031c5a..00000000
--- a/src/helpers/project/dctypes/single_service.rs
+++ /dev/null
@@ -1,8 +0,0 @@
-use serde::{Deserialize, Serialize};
-use crate::helpers::project::dctypes;
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
-pub struct SingleService {
-    pub service: dctypes::Service,
-}
-
diff --git a/src/helpers/project/dctypes/sys_ctls.rs b/src/helpers/project/dctypes/sys_ctls.rs
deleted file mode 100644
index 4abbb24e..00000000
--- a/src/helpers/project/dctypes/sys_ctls.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-use serde::{Deserialize, Serialize};
-use crate::helpers::project::dctypes;
-#[cfg(feature = "indexmap")]
-use indexmap::IndexMap;
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
-#[serde(untagged)]
-pub enum SysCtls {
-    List(Vec<String>),
-    #[cfg(feature = "indexmap")]
-    Map(IndexMap<String, Option<dctypes::SingleValue>>),
-    #[cfg(not(feature = "indexmap"))]
-    Map(HashMap<String, Option<dctypes::SingleValue>>),
-}
-
-impl Default for SysCtls {
-    fn default() -> Self {
-        Self::List(Vec::new())
-    }
-}
-
-impl SysCtls {
-    pub fn is_empty(&self) -> bool {
-        match self {
-            Self::List(v) => v.is_empty(),
-            Self::Map(m) => m.is_empty(),
-        }
-    }
-}
-
diff --git a/src/helpers/project/dctypes/tmpfs.rs b/src/helpers/project/dctypes/tmpfs.rs
deleted file mode 100644
index 1bea9b6e..00000000
--- a/src/helpers/project/dctypes/tmpfs.rs
+++ /dev/null
@@ -1,9 +0,0 @@
-use serde::{Deserialize, Serialize};
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
-#[serde(untagged)]
-pub enum Tmpfs {
-    Simple(String),
-    List(Vec<String>),
-}
-
diff --git a/src/helpers/project/dctypes/top_level_volumes.rs b/src/helpers/project/dctypes/top_level_volumes.rs
deleted file mode 100644
index 304d531d..00000000
--- a/src/helpers/project/dctypes/top_level_volumes.rs
+++ /dev/null
@@ -1,19 +0,0 @@
-use serde::{Deserialize, Serialize};
-#[cfg(feature = "indexmap")]
-use indexmap::IndexMap;
-#[cfg(not(feature = "indexmap"))]
-use std::collections::HashMap;
-use crate::helpers::project::dctypes::*;
-
-#[cfg(feature = "indexmap")]
-#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
-pub struct TopLevelVolumes(pub IndexMap<String, MapOrEmpty<ComposeVolume>>);
-#[cfg(not(feature = "indexmap"))]
-#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
-pub struct TopLevelVolumes(pub HashMap<String, MapOrEmpty<ComposeVolume>>);
-
-impl TopLevelVolumes {
-    pub fn is_empty(&self) -> bool {
-        self.0.is_empty()
-    }
-}
diff --git a/src/helpers/project/dctypes/ulimit.rs b/src/helpers/project/dctypes/ulimit.rs
deleted file mode 100644
index 65899310..00000000
--- a/src/helpers/project/dctypes/ulimit.rs
+++ /dev/null
@@ -1,8 +0,0 @@
-use serde::{Deserialize, Serialize};
-
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
-#[serde(untagged)]
-pub enum Ulimit {
-    Single(i64),
-    SoftHard { soft: i64, hard: i64 },
-}
diff --git a/src/helpers/project/dctypes/ulimits.rs b/src/helpers/project/dctypes/ulimits.rs
deleted file mode 100644
index 2cfe4f51..00000000
--- a/src/helpers/project/dctypes/ulimits.rs
+++ /dev/null
@@ -1,20 +0,0 @@
-use serde::{Deserialize, Serialize};
-#[cfg(feature = "indexmap")]
-use indexmap::IndexMap;
-#[cfg(not(feature = "indexmap"))]
-use std::collections::HashMap;
-use crate::helpers::project::dctypes;
-
-#[cfg(feature = "indexmap")]
-#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
-pub struct Ulimits(pub IndexMap<String, dctypes::Ulimit>);
-#[cfg(not(feature = "indexmap"))]
-#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
-pub struct Ulimits(pub HashMap<String, dctypes::Ulimit>);
-
-impl Ulimits {
-    pub fn is_empty(&self) -> bool {
-        self.0.is_empty()
-    }
-}
-
diff --git a/tests/model_user_stack.rs b/tests/model_project.rs
similarity index 100%
rename from tests/model_user_stack.rs
rename to tests/model_project.rs

From 96c84b6474cce4680cc60df873bb4b20b0a896c9 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Thu, 29 Feb 2024 17:07:56 +0200
Subject: [PATCH 195/284] form updates

---
 docker-compose.yml                                        | 1 +
 docker/dev/docker-compose.yml                             | 3 ++-
 src/forms/cloud.rs                                        | 3 ---
 src/forms/project/volume.rs                               | 4 ++--
 src/forms/server.rs                                       | 3 ---
 src/routes/cloud/add.rs                                   | 8 --------
 src/routes/server/add.rs                                  | 8 --------
 tests/cloud.rs                                            | 0
 tests/{ => mock_data}/app.json                            | 0
 .../{ => mock_data}/custom-stack-payload-no-networks.json | 0
 tests/{ => mock_data}/custom-stack-payload.json           | 0
 tests/{ => mock_data}/custom.json                         | 0
 tests/{ => mock_data}/web-item.json                       | 0
 13 files changed, 5 insertions(+), 25 deletions(-)
 create mode 100644 tests/cloud.rs
 rename tests/{ => mock_data}/app.json (100%)
 rename tests/{ => mock_data}/custom-stack-payload-no-networks.json (100%)
 rename tests/{ => mock_data}/custom-stack-payload.json (100%)
 rename tests/{ => mock_data}/custom.json (100%)
 rename tests/{ => mock_data}/web-item.json (100%)

diff --git a/docker-compose.yml b/docker-compose.yml
index 467836ef..e9aee75a 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -15,6 +15,7 @@ services:
     volumes:
       - ./files:/app/files
       - ./docker/local/configuration.yaml:/app/configuration.yaml
+      - ./access_control.conf:/app/access_control.conf
       - ./migrations:/app/migrations
       - ./docker/local/.env:/app/.env
     ports:
diff --git a/docker/dev/docker-compose.yml b/docker/dev/docker-compose.yml
index c1a022b2..6bf8eb55 100644
--- a/docker/dev/docker-compose.yml
+++ b/docker/dev/docker-compose.yml
@@ -13,13 +13,14 @@ networks:
 services:
 
   stacker:
-    image: trydirect/stacker:0.0.5
+    image: trydirect/stacker:0.0.7
     build: .
     container_name: stacker
     restart: always
     volumes:
       - ./stacker/files:/app/files
       - ./configuration.yaml:/app/configuration.yaml
+      - ./access_control.conf:/app/access_control.conf
       - ./migrations:/app/migrations
       - ./.env:/app/.env
     ports:
diff --git a/src/forms/cloud.rs b/src/forms/cloud.rs
index e742952d..e0fafd9a 100644
--- a/src/forms/cloud.rs
+++ b/src/forms/cloud.rs
@@ -5,7 +5,6 @@ use chrono::{DateTime, Utc};
 
 #[derive(Serialize, Deserialize, Debug, Validate)]
 pub struct Cloud {
-    pub id: i32,
     pub user_id: String,
     #[validate(min_length = 2)]
     #[validate(max_length = 50)]
@@ -14,8 +13,6 @@ pub struct Cloud {
     pub cloud_key: Option<String>,
     pub cloud_secret: Option<String>,
     pub save_token: Option<bool>,
-    pub created_at: DateTime<Utc>,
-    pub updated_at: DateTime<Utc>,
 }
 
 impl Into<models::Cloud> for Cloud {
diff --git a/src/forms/project/volume.rs b/src/forms/project/volume.rs
index b4bfaedb..2b30a594 100644
--- a/src/forms/project/volume.rs
+++ b/src/forms/project/volume.rs
@@ -4,8 +4,8 @@ use indexmap::IndexMap;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct Volume {
-    pub(crate) host_path: Option<String>,
-    pub(crate) container_path: Option<String>,
+    pub host_path: Option<String>,
+    pub container_path: Option<String>,
 }
 
 impl Volume {
diff --git a/src/forms/server.rs b/src/forms/server.rs
index 7b2672b2..edfe8270 100644
--- a/src/forms/server.rs
+++ b/src/forms/server.rs
@@ -5,7 +5,6 @@ use chrono::{DateTime, Utc};
 
 #[derive(Serialize, Deserialize, Debug, Validate)]
 pub struct Server {
-    pub id: i32,
     pub user_id: String,
     pub cloud_id: i32,
     pub project_id: i32,
@@ -14,8 +13,6 @@ pub struct Server {
     pub server: String,
     pub os: String,
     pub disk_type: Option<String>,
-    pub created_at: DateTime<Utc>,
-    pub updated_at: DateTime<Utc>,
 }
 
 impl Into<models::Server> for Server {
diff --git a/src/routes/cloud/add.rs b/src/routes/cloud/add.rs
index 4923cad9..cb572096 100644
--- a/src/routes/cloud/add.rs
+++ b/src/routes/cloud/add.rs
@@ -29,14 +29,6 @@ pub async fn add(
         return Err(JsonResponse::<models::Project>::build().form_error(errors));
     }
 
-    let cloud = db::cloud::fetch(pg_pool.get_ref(), form.id)
-        .await
-        .map_err(|_msg| JsonResponse::<models::Cloud>::build().internal_server_error(_msg))?
-        .ok_or_else(|| JsonResponse::<models::Cloud>::build().not_found("not found"))?
-        ;
-
-    tracing::debug!("Cloud record is found ? {:?}", cloud);
-
     let mut cloud: models::Cloud = form.into_inner().into();
     cloud.user_id = user.id.clone();
 
diff --git a/src/routes/server/add.rs b/src/routes/server/add.rs
index 6dbd2d06..278f4e9a 100644
--- a/src/routes/server/add.rs
+++ b/src/routes/server/add.rs
@@ -29,14 +29,6 @@ pub async fn add(
         return Err(JsonResponse::<models::Project>::build().form_error(errors));
     }
 
-    let server = db::server::fetch(pg_pool.get_ref(), form.id)
-        .await
-        .map_err(|_msg| JsonResponse::<models::Server>::build().internal_server_error(_msg))?
-        .ok_or_else(|| JsonResponse::<models::Server>::build().not_found("not found"))?
-        ;
-
-    tracing::debug!("Server record is found ? {:?}", server);
-
     let mut server: models::Server = form.into_inner().into();
     server.user_id = user.id.clone();
 
diff --git a/tests/cloud.rs b/tests/cloud.rs
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/app.json b/tests/mock_data/app.json
similarity index 100%
rename from tests/app.json
rename to tests/mock_data/app.json
diff --git a/tests/custom-stack-payload-no-networks.json b/tests/mock_data/custom-stack-payload-no-networks.json
similarity index 100%
rename from tests/custom-stack-payload-no-networks.json
rename to tests/mock_data/custom-stack-payload-no-networks.json
diff --git a/tests/custom-stack-payload.json b/tests/mock_data/custom-stack-payload.json
similarity index 100%
rename from tests/custom-stack-payload.json
rename to tests/mock_data/custom-stack-payload.json
diff --git a/tests/custom.json b/tests/mock_data/custom.json
similarity index 100%
rename from tests/custom.json
rename to tests/mock_data/custom.json
diff --git a/tests/web-item.json b/tests/mock_data/web-item.json
similarity index 100%
rename from tests/web-item.json
rename to tests/mock_data/web-item.json

From 10d727518b6959737c94618dbff0179ab178b7dd Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Fri, 1 Mar 2024 13:57:02 +0200
Subject: [PATCH 196/284] form field refactoring, servers_count removed

---
 ...0230905145525_creating_stack_tables.up.sql |  2 +-
 ...0240229080559_creating_cloud_server.up.sql |  2 +-
 ...eating_user_stack_server_relation.down.sql |  2 -
 ...creating_user_stack_server_relation.up.sql |  2 -
 src/db/project.rs                             | 72 +++++++++----------
 src/forms/project/custom.rs                   |  3 -
 src/forms/project/form.rs                     | 38 +++++-----
 src/forms/project/payload.rs                  |  1 -
 src/models/project.rs                         |  1 +
 src/routes/project/add.rs                     | 17 +----
 src/routes/project/delete.rs                  | 43 +++++++----
 src/routes/project/deploy.rs                  | 32 ++++++++-
 src/routes/project/mod.rs                     |  2 +-
 src/routes/project/update.rs                  |  2 +-
 src/startup.rs                                |  5 +-
 tests/cloud.rs                                | 48 +++++++++++++
 tests/dockerhub.rs                            |  6 +-
 17 files changed, 169 insertions(+), 109 deletions(-)
 delete mode 100644 migrations/20240229083517_creating_user_stack_server_relation.down.sql
 delete mode 100644 migrations/20240229083517_creating_user_stack_server_relation.up.sql

diff --git a/migrations/20230905145525_creating_stack_tables.up.sql b/migrations/20230905145525_creating_stack_tables.up.sql
index 333b27f4..6ae6433e 100644
--- a/migrations/20230905145525_creating_stack_tables.up.sql
+++ b/migrations/20230905145525_creating_stack_tables.up.sql
@@ -2,7 +2,7 @@ CREATE TABLE project (
     id serial4 NOT NULL,
     stack_id uuid NOT NULL,
     user_id VARCHAR(50) NOT NULL,
-    name TEXT NOT NULL UNIQUE,
+    name TEXT,
     body JSON NOT NULL,
     created_at timestamptz NOT NULL,
     updated_at timestamptz NOT NULL,
diff --git a/migrations/20240229080559_creating_cloud_server.up.sql b/migrations/20240229080559_creating_cloud_server.up.sql
index 091fb8f3..e4ed91bb 100644
--- a/migrations/20240229080559_creating_cloud_server.up.sql
+++ b/migrations/20240229080559_creating_cloud_server.up.sql
@@ -14,7 +14,7 @@ CREATE TABLE server (
        updated_at timestamptz NOT NULL,
        CONSTRAINT user_server_pkey PRIMARY KEY (id),
        CONSTRAINT fk_server FOREIGN KEY(cloud_id) REFERENCES cloud(id),
-       CONSTRAINT fk_server_project FOREIGN KEY(project_id) REFERENCES project(id)
+       CONSTRAINT fk_server_project FOREIGN KEY(project_id) REFERENCES project(id)  ON UPDATE CASCADE ON DELETE CASCADE
 );
 
 CREATE INDEX idx_server_user_id ON server(user_id);
diff --git a/migrations/20240229083517_creating_user_stack_server_relation.down.sql b/migrations/20240229083517_creating_user_stack_server_relation.down.sql
deleted file mode 100644
index c410fff5..00000000
--- a/migrations/20240229083517_creating_user_stack_server_relation.down.sql
+++ /dev/null
@@ -1,2 +0,0 @@
--- Add down migration script here
-ALTER table server DROP COLUMN project_id;
diff --git a/migrations/20240229083517_creating_user_stack_server_relation.up.sql b/migrations/20240229083517_creating_user_stack_server_relation.up.sql
deleted file mode 100644
index ab021b17..00000000
--- a/migrations/20240229083517_creating_user_stack_server_relation.up.sql
+++ /dev/null
@@ -1,2 +0,0 @@
--- Add up migration script here
-ALTER table server ADD COLUMN project_id integer CONSTRAINT project_id REFERENCES project(id) ON UPDATE CASCADE ON DELETE CASCADE;
diff --git a/src/db/project.rs b/src/db/project.rs
index 99a5d920..8104b061 100644
--- a/src/db/project.rs
+++ b/src/db/project.rs
@@ -143,52 +143,44 @@ pub async fn update(pool: &PgPool, mut project: models::Project) -> Result<model
     })
 }
 
-pub async fn delete(pool: &PgPool, id: i32) -> Result<Option<PgRow>, String> {
+pub async fn delete(pool: &PgPool, id: i32) -> Result<bool, String> {
     tracing::info!("Delete project {}", id);
-    let mut tx = pool.begin().await
-    .map(|result| result)
-    .map_err(|err| {
-        tracing::error!("Failed to execute query: {:?}", err);
-        "".to_string()
-    }).unwrap();
+    let mut tx = match pool.begin().await {
+        Ok(result) => result,
+        Err(err) => {
+            tracing::error!("Failed to begin transaction: {:?}", err);
+            return Err("".to_string());
+        }
+    };
 
-    // delete records from deployment
-    // delete records from server
-    sqlx::query("DELETE FROM deployment where project_id=?")
-        .bind(id)
-        .execute(&mut *tx)
-        .await
-        .map(|result| result)
-        .map_err(|err| {
-            tracing::error!("Failed to execute query: {:?}", err);
-            "".to_string()
-        }).unwrap();
+    // Combine delete queries into a single query
+    let delete_query = "
+        DELETE FROM deployment WHERE project_id = $1;
+        DELETE FROM server WHERE project_id = $1;
+        DELETE FROM project WHERE id = $1;
+    ";
 
-    sqlx::query("DELETE FROM server where project_id=?")
+    match sqlx::query(delete_query)
         .bind(id)
-        .execute(&mut *tx)
+        .execute(&mut tx)
         .await
-        .map(|result| result)
         .map_err(|err| {
-            tracing::error!("Failed to execute query: {:?}", err);
-            "".to_string()
-        }).unwrap();
-
-    sqlx::query("DELETE FROM project where id=?")
-        .bind(id)
-        .execute(&mut *tx)
-        .await
-        .map(|result| result)
-        .map_err(|err| {
-            tracing::error!("Failed to execute query: {:?}", err);
-            "".to_string()
-        }).unwrap();
-
-    tx.commit().await;
-
-    // tracing::error!("Failed to delete project, error: {:?}", e);
-    // Err("Could not fetch data".to_string())
+            println!("{:?}", err)
+        })
+    {
+        Ok(_) => {
+            tx.commit().await.map_err(|err| {
+                tracing::error!("Failed to commit transaction: {:?}", err);
+                false
+            });
+            Ok(true)
+        }
+        Err(err) => {
+            tx.rollback().await.map_err(|err| println!("{:?}", err));
+            Ok(false)
+        }
+    }
+    // Ok(true)
 
-    Ok(None)
 }
 
diff --git a/src/forms/project/custom.rs b/src/forms/project/custom.rs
index 8be69cc7..0a4eac7e 100644
--- a/src/forms/project/custom.rs
+++ b/src/forms/project/custom.rs
@@ -13,9 +13,6 @@ pub struct Custom {
     pub feature: Option<Vec<forms::project::Feature>>,
     #[validate]
     pub service: Option<Vec<forms::project::Service>>,
-    #[validate(minimum = 0)]
-    #[validate(maximum = 10)]
-    pub servers_count: u32,
     #[validate(min_length = 3)]
     #[validate(max_length = 50)]
     pub custom_stack_code: String,
diff --git a/src/forms/project/form.rs b/src/forms/project/form.rs
index 2ae56bd0..4321f33b 100644
--- a/src/forms/project/form.rs
+++ b/src/forms/project/form.rs
@@ -25,9 +25,6 @@ pub struct ProjectForm {
     pub extended_features: Option<Vec<Value>>,
     pub subscriptions: Option<Vec<String>>,
     pub form_app: Option<Vec<String>>,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 50)]
-    pub selected_plan: String,
     pub custom: forms::project::Custom,
 }
 
@@ -49,24 +46,23 @@ impl ProjectForm {
             }
         }
 
-        // temporarily disabled
-        // if let Some(service) = &self.custom.service {
-        //     for app in service {
-        //         if !app.app.docker_image.is_active().await? {
-        //             is_active = false;
-        //             break;
-        //         }
-        //     }
-        // }
-        //
-        // if let Some(features) = &self.custom.feature {
-        //     for app in features {
-        //         if !app.app.docker_image.is_active().await? {
-        //             is_active = false;
-        //             break;
-        //         }
-        //     }
-        // }
+        if let Some(service) = &self.custom.service {
+            for app in service {
+                if !app.app.docker_image.is_active().await? {
+                    is_active = false;
+                    break;
+                }
+            }
+        }
+
+        if let Some(features) = &self.custom.feature {
+            for app in features {
+                if !app.app.docker_image.is_active().await? {
+                    is_active = false;
+                    break;
+                }
+            }
+        }
         Ok(is_active)
     }
 }
diff --git a/src/forms/project/payload.rs b/src/forms/project/payload.rs
index 70800fed..70e8f60b 100644
--- a/src/forms/project/payload.rs
+++ b/src/forms/project/payload.rs
@@ -28,7 +28,6 @@ pub struct Payload {
     #[serde(flatten)]
     pub cloud: models::Cloud,
     pub stack_code: String,
-    pub selected_plan: String,
     pub custom: forms::project::Custom,
     pub docker_compose: Option<Vec<u8>>,
 }
diff --git a/src/models/project.rs b/src/models/project.rs
index 35661eb2..06d29053 100644
--- a/src/models/project.rs
+++ b/src/models/project.rs
@@ -2,6 +2,7 @@ use chrono::{DateTime, Utc};
 use serde::{Deserialize, Serialize};
 use serde_json::Value;
 use uuid::Uuid;
+use crate::db;
 
 #[derive(Debug, Serialize, Deserialize, Clone)]
 pub struct Project {
diff --git a/src/routes/project/add.rs b/src/routes/project/add.rs
index e3d1b076..05fb6cf0 100644
--- a/src/routes/project/add.rs
+++ b/src/routes/project/add.rs
@@ -16,7 +16,7 @@ use std::sync::Arc;
 
 #[tracing::instrument(name = "Add project.")]
 #[post("")]
-pub async fn add(
+pub async fn item(
     body: Bytes,
     user: web::ReqData<Arc<models::User>>,
     pg_pool: Data<PgPool>,
@@ -25,8 +25,6 @@ pub async fn add(
     let form = body_into_form(body).await?;
     let project_name = form.custom.custom_stack_code.clone();
 
-    project_exists(pg_pool.get_ref(), &project_name).await?;
-
     let body: Value = serde_json::to_value::<forms::project::ProjectForm>(form)
         .or(serde_json::to_value::<forms::project::ProjectForm>(forms::project::ProjectForm::default()))
         .unwrap();
@@ -41,19 +39,6 @@ pub async fn add(
 }
 
 
-async fn project_exists(pool: &PgPool, project_name: &String) -> Result<(), Error> {
-    db::project::fetch_one_by_name(pool, project_name)
-        .await
-        .map_err(|_| {
-            JsonResponse::<models::Project>::build().internal_server_error("Internal Server Error")
-        })
-        .and_then(|project| match project {
-            Some(_) => Err(JsonResponse::<models::Project>::build()
-                .conflict("Project with that name already exists")),
-            None => Ok(()),
-        })
-}
-
 async fn body_into_form(body: Bytes) -> Result<forms::project::ProjectForm, Error> {
     let body_bytes = actix_web::body::to_bytes(body).await.unwrap();
     let body_str = str::from_utf8(&body_bytes)
diff --git a/src/routes/project/delete.rs b/src/routes/project/delete.rs
index fb4ff336..16cc579a 100644
--- a/src/routes/project/delete.rs
+++ b/src/routes/project/delete.rs
@@ -1,12 +1,13 @@
-use crate::db;
 use crate::helpers::JsonResponse;
 use crate::models;
 use actix_web::{delete, web, Responder, Result};
 use sqlx::PgPool;
 use std::sync::Arc;
 use tracing::Instrument;
+use crate::db;
+use crate::models::Project;
 
-#[tracing::instrument(name = "Get logged user project.")]
+#[tracing::instrument(name = "Delete project of a user.")]
 #[delete("/{id}")]
 pub async fn item(
     user: web::ReqData<Arc<models::User>>,
@@ -16,18 +17,32 @@ pub async fn item(
     /// Get project apps of logged user only
     let (id,) = path.into_inner();
 
-    db::project::fetch(pg_pool.get_ref(), id)
-        .await
+    let project = db::project::fetch(pg_pool.get_ref(), id).await
         .map_err(|err| JsonResponse::<models::Project>::build().internal_server_error(err))
-        .and_then(|project| match project {
-            Some(project) if project.user_id != user.id => {
-                Err(JsonResponse::<models::Project>::build().not_found("not found"))
-            }
-            Some(project) => {
-                db::project::delete(pg_pool.get_ref(), id);
-                Ok(JsonResponse::build().set_item(Some(project)).ok("Deleted"))
-            },
-            None => Err(JsonResponse::<models::Project>::build().not_found("not found")),
-        })
+        .map(|project| { project }).unwrap();
+    //
+    //         match project {
+    //             Some(project) if project.user_id != user.id => {
+    //                 Err(JsonResponse::<models::Project>::build().not_found("not found"))
+    //             }
+    //             Some(project) => {
+    //
+    //                     match db::project::delete(pg_pool.get_ref(), id)
+    //                         .await
+    //                         .map_err(|e| println!("{:?}", e))
+    //                     {
+    //                         Ok(true) => {
+    //                             Ok(JsonResponse::build().set_item(Some(project)).ok("Deleted"))
+    //                         }
+    //                         _ => {
+    //                             Err(JsonResponse::<models::Project>::build().bad_request("Could not delete"))
+    //                         }
+    //                     }
+    //             },
+    //             None => Err(JsonResponse::<models::Project>::build().not_found("not found")),
+    //         }
+    //     })
+
 
+    Ok(JsonResponse::<Project>::build().ok("Deleted"))
 }
diff --git a/src/routes/project/deploy.rs b/src/routes/project/deploy.rs
index 265957a0..f3144f06 100644
--- a/src/routes/project/deploy.rs
+++ b/src/routes/project/deploy.rs
@@ -42,8 +42,36 @@ pub async fn add(
     // let compressed = fc.unwrap_or("".to_string());
     project_data.docker_compose = Some(compress(fc.as_str()));
 
-    // project_data.cloud =
-    // project_data.server =
+    if let Some(cloud_id) = dc.project.cloud_id {
+        project_data.cloud = match db::cloud::fetch(pg_pool.get_ref(), cloud_id).await {
+            Ok(cloud) => {
+                match cloud {
+                    Some(cloud) => cloud,
+                    None => {
+                        return Err(JsonResponse::<models::Project>::build().not_found("No cloud configured"));
+                    }
+                }
+            }
+            Err(e) => {
+                return Err(JsonResponse::<models::Project>::build().not_found("No cloud configured"));
+            }
+        };
+
+        project_data.server = match db::server::fetch_by_project(pg_pool.get_ref(), dc.project.id.clone()).await {
+            Ok(server) => {
+                // for now we support only one type of servers
+                // if let Some(server) = server.into_iter().nth(0) {
+                //     server
+                // }
+                server.into_iter().nth(0).unwrap() // @todo refactoring is required
+            }
+            Err(err) => {
+                return Err(JsonResponse::<models::Project>::build().not_found("No servers configured"));
+            }
+        }
+    } else {
+        return Err(JsonResponse::<models::Project>::build().not_found("No cloud provider configured"));
+    }
 
     let project_id = dc.project.id.clone();
     let json_request = dc.project.body.clone();
diff --git a/src/routes/project/mod.rs b/src/routes/project/mod.rs
index 2d41c67d..6d66205d 100644
--- a/src/routes/project/mod.rs
+++ b/src/routes/project/mod.rs
@@ -3,7 +3,7 @@ pub mod deploy;
 pub mod get;
 pub mod update;
 pub(crate) mod compose;
-mod delete;
+pub(crate) mod delete;
 
 pub use add::*;
 pub use update::*;
diff --git a/src/routes/project/update.rs b/src/routes/project/update.rs
index 5a8dac67..e20d9090 100644
--- a/src/routes/project/update.rs
+++ b/src/routes/project/update.rs
@@ -12,7 +12,7 @@ use uuid::Uuid;
 
 #[tracing::instrument(name = "Update project.")]
 #[post("/{id}")]
-pub async fn update(
+pub async fn item(
     path: web::Path<(i32,)>,
     form: web::Json<forms::project::ProjectForm>,
     user: web::ReqData<Arc<models::User>>,
diff --git a/src/startup.rs b/src/startup.rs
index e489cb95..3c8165c5 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -76,8 +76,9 @@ pub async fn run(
                     .service(crate::routes::project::compose::admin)
                     .service(crate::routes::project::get::item)
                     .service(crate::routes::project::get::list)
-                    .service(crate::routes::project::add::add)
-                    .service(crate::routes::project::update::update),
+                    .service(crate::routes::project::add::item)
+                    .service(crate::routes::project::update::item)
+                    .service(crate::routes::project::delete::item),
             )
             .service(
                 web::scope("/cloud")
diff --git a/tests/cloud.rs b/tests/cloud.rs
index e69de29b..c3fd2d39 100644
--- a/tests/cloud.rs
+++ b/tests/cloud.rs
@@ -0,0 +1,48 @@
+mod common;
+
+// test me: cargo t --test cloud -- --nocapture --show-output
+#[tokio::test]
+async fn list() {
+
+    let app = common::spawn_app().await; // server
+    let client = reqwest::Client::new(); // client
+
+    let response = client
+        .get(&format!("{}/cloud", &app.address))
+        .send()
+        .await
+        .expect("Failed to execute request.");
+
+    assert!(response.status().is_success());
+    assert_eq!(Some(0), response.content_length());
+}
+
+// test me: cargo t --test cloud add_cloud -- --nocapture --show-output
+#[tokio::test]
+async fn add_cloud() {
+
+    let app = common::spawn_app().await; // server
+    let client = reqwest::Client::new(); // client
+
+    let data = r#"
+    {
+        "user_id": "fake_user_id",
+        "provider": "htz",
+        "cloud_token": "",
+        "cloud_key": "",
+        "cloud_secret": "",
+        "save_token": true
+    }
+    "#;
+
+    let response = client
+        .post(&format!("{}/cloud", &app.address))
+        .json(data)
+        .send()
+        .await
+        .expect("Failed to execute request.");
+
+    println!("response: {}", response.status());
+    assert!(response.status().is_success());
+    assert_eq!(Some(0), response.content_length());
+}
diff --git a/tests/dockerhub.rs b/tests/dockerhub.rs
index 4134309e..25b30e25 100644
--- a/tests/dockerhub.rs
+++ b/tests/dockerhub.rs
@@ -1,11 +1,13 @@
 // use std::fs;
 // use std::collections::HashMap;
 use std::env;
+use docker_compose_types::{ComposeVolume, SingleValue};
+
 mod common;
 use stacker::forms::project::DockerImage;
-use stacker::helpers::project::dctypes::{ComposeVolume, SingleValue};
+// use stacker::helpers::project::dctypes::{ComposeVolume, SingleValue};
 use serde_yaml;
-use stacker::forms::Volume;
+use stacker::forms::project::Volume;
 
 const DOCKER_USERNAME: &str = "trydirect";
 const DOCKER_PASSWORD: &str = "***************";

From 47eac8fcf274bf1cb973797177c8354e671fd402 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Fri, 1 Mar 2024 16:54:41 +0200
Subject: [PATCH 197/284] 30-access-policies admin enable client

---
 src/routes/client/enable.rs | 30 ++++++++++++++++++++++++++----
 src/startup.rs              |  2 --
 2 files changed, 26 insertions(+), 6 deletions(-)

diff --git a/src/routes/client/enable.rs b/src/routes/client/enable.rs
index c390a4ef..79764110 100644
--- a/src/routes/client/enable.rs
+++ b/src/routes/client/enable.rs
@@ -8,7 +8,7 @@ use sqlx::PgPool;
 use std::sync::Arc;
 use tracing::Instrument;
 
-#[tracing::instrument(name = "Enable client.")]
+#[tracing::instrument(name = "User enable client.")]
 #[put("/{id}/enable")]
 pub async fn enable_handler(
     user: web::ReqData<Arc<models::User>>,
@@ -16,15 +16,37 @@ pub async fn enable_handler(
     pg_pool: web::Data<PgPool>,
     path: web::Path<(i32,)>,
 ) -> Result<impl Responder> {
-    println!("user.id {}", user.id);
-    //todo the owner
-    //todo add admin endpoint
     let client_id = path.0;
     let mut client = db::client::fetch(pg_pool.get_ref(), client_id)
         .await
         .map_err(|msg| JsonResponse::<models::Client>::build().internal_server_error(msg))?
         .ok_or_else(|| JsonResponse::<models::Client>::build().not_found("not found"))?;
 
+    if client.user_id != user.id {
+        return Err(JsonResponse::<models::Client>::build().bad_request("client is not the owner"));
+    }
+
+    enable_client(pg_pool, client).await
+}
+
+#[tracing::instrument(name = "Admin enable client.")]
+#[put("/{id}/enable")]
+pub async fn admin_enable_handler(
+    user: web::ReqData<Arc<models::User>>,
+    settings: web::Data<Settings>,
+    pg_pool: web::Data<PgPool>,
+    path: web::Path<(i32,)>,
+) -> Result<impl Responder> {
+    let client_id = path.0;
+    let mut client = db::client::fetch(pg_pool.get_ref(), client_id)
+        .await
+        .map_err(|msg| JsonResponse::<models::Client>::build().internal_server_error(msg))?
+        .ok_or_else(|| JsonResponse::<models::Client>::build().not_found("not found"))?;
+
+    enable_client(pg_pool, client).await
+}
+
+async fn enable_client(pg_pool: web::Data<PgPool>, mut client: models::Client) -> Result<impl Responder> {
     if client.secret.is_some() {
         return Err(JsonResponse::<models::Client>::build().bad_request("client is already active"));
     }
diff --git a/src/startup.rs b/src/startup.rs
index 5f0b30b8..7e3786dc 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -41,12 +41,10 @@ pub async fn run(
                     .service(routes::client::enable_handler)
                     .service(routes::client::disable_handler),
             )
-            /*
             .service(
                 web::scope("/admin/client")
                     .service(routes::client::admin_enable_handler)
             )
-            */
             .service(
                 web::scope("/test").service(routes::test::deploy::handler),
             )

From b9e6284ff791af65470eed20bcecf65ed8acfb20 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 2 Mar 2024 16:36:36 +0200
Subject: [PATCH 198/284] 30-access-policies client admin

---
 src/routes/client/disable.rs | 31 +++++++++++++++++++++++++++----
 src/routes/client/enable.rs  | 10 +++++-----
 src/routes/client/update.rs  | 33 ++++++++++++++++++++++++++++-----
 src/startup.rs               |  2 ++
 4 files changed, 62 insertions(+), 14 deletions(-)

diff --git a/src/routes/client/disable.rs b/src/routes/client/disable.rs
index 3c46fa65..892d3886 100644
--- a/src/routes/client/disable.rs
+++ b/src/routes/client/disable.rs
@@ -7,7 +7,7 @@ use sqlx::PgPool;
 use std::sync::Arc;
 use tracing::Instrument;
 
-#[tracing::instrument(name = "Disable client.")]
+#[tracing::instrument(name = "User disable client.")]
 #[put("/{id}/disable")]
 pub async fn disable_handler(
     user: web::ReqData<Arc<models::User>>,
@@ -15,20 +15,43 @@ pub async fn disable_handler(
     pg_pool: web::Data<PgPool>,
     path: web::Path<(i32,)>,
 ) -> Result<impl Responder> {
-    //todo. the owner
-    //todo. add admin endpoint
     let client_id = path.0;
     let mut client = db::client::fetch(pg_pool.get_ref(), client_id)
         .await
         .map_err(|msg| JsonResponse::<models::Client>::build().internal_server_error(msg))?
         .ok_or_else(|| JsonResponse::<models::Client>::build().not_found("not found"))?;
 
+    if client.user_id != user.id {
+        return Err(JsonResponse::<models::Client>::build().bad_request("client is not the owner"));
+    }
+
+    disable_client(pg_pool.get_ref(), client).await
+}
+
+#[tracing::instrument(name = "Admin disable client.")]
+#[put("/{id}/disable")]
+pub async fn admin_disable_handler(
+    user: web::ReqData<Arc<models::User>>,
+    settings: web::Data<Settings>,
+    pg_pool: web::Data<PgPool>,
+    path: web::Path<(i32,)>,
+) -> Result<impl Responder> {
+    let client_id = path.0;
+    let mut client = db::client::fetch(pg_pool.get_ref(), client_id)
+        .await
+        .map_err(|msg| JsonResponse::<models::Client>::build().internal_server_error(msg))?
+        .ok_or_else(|| JsonResponse::<models::Client>::build().not_found("not found"))?;
+
+    disable_client(pg_pool.get_ref(), client).await
+}
+
+async fn disable_client(pg_pool: &PgPool, mut client: models::Client) -> Result<impl Responder> {
     if client.secret.is_none() {
         return Err(JsonResponse::<models::Client>::build().bad_request("client is not active"));
     }
 
     client.secret = None;
-    db::client::update(pg_pool.get_ref(), client)
+    db::client::update(pg_pool, client)
         .await
         .map(|client| JsonResponse::build().set_item(client).ok("success"))
         .map_err(|msg| JsonResponse::<models::Client>::build().bad_request(msg))
diff --git a/src/routes/client/enable.rs b/src/routes/client/enable.rs
index 79764110..5fb3c642 100644
--- a/src/routes/client/enable.rs
+++ b/src/routes/client/enable.rs
@@ -26,7 +26,7 @@ pub async fn enable_handler(
         return Err(JsonResponse::<models::Client>::build().bad_request("client is not the owner"));
     }
 
-    enable_client(pg_pool, client).await
+    enable_client(pg_pool.get_ref(), client).await
 }
 
 #[tracing::instrument(name = "Admin enable client.")]
@@ -43,20 +43,20 @@ pub async fn admin_enable_handler(
         .map_err(|msg| JsonResponse::<models::Client>::build().internal_server_error(msg))?
         .ok_or_else(|| JsonResponse::<models::Client>::build().not_found("not found"))?;
 
-    enable_client(pg_pool, client).await
+    enable_client(pg_pool.get_ref(), client).await
 }
 
-async fn enable_client(pg_pool: web::Data<PgPool>, mut client: models::Client) -> Result<impl Responder> {
+async fn enable_client(pg_pool: &PgPool, mut client: models::Client) -> Result<impl Responder> {
     if client.secret.is_some() {
         return Err(JsonResponse::<models::Client>::build().bad_request("client is already active"));
     }
 
-    client.secret = helpers::client::generate_secret(pg_pool.get_ref(), 255)
+    client.secret = helpers::client::generate_secret(pg_pool, 255)
         .await
         .map(|secret| Some(secret))
         .map_err(|err| JsonResponse::<models::Client>::build().bad_request(err))?;
 
-    db::client::update(pg_pool.get_ref(), client)
+    db::client::update(pg_pool, client)
         .await
         .map(|client| JsonResponse::build().set_item(client).ok("success"))
         .map_err(|err| JsonResponse::<models::Client>::build().bad_request(err))
diff --git a/src/routes/client/update.rs b/src/routes/client/update.rs
index fe8db12e..506fd53f 100644
--- a/src/routes/client/update.rs
+++ b/src/routes/client/update.rs
@@ -7,7 +7,7 @@ use sqlx::PgPool;
 use std::sync::Arc;
 use tracing::Instrument;
 
-#[tracing::instrument(name = "Update client.")]
+#[tracing::instrument(name = "User update client.")]
 #[put("/{id}")]
 pub async fn update_handler(
     user: web::ReqData<Arc<models::User>>,
@@ -15,24 +15,47 @@ pub async fn update_handler(
     pg_pool: web::Data<PgPool>,
     path: web::Path<(i32,)>,
 ) -> Result<impl Responder> {
-    //todo owner
-    //todo admin endpoint
     let client_id = path.0;
     let mut client = db::client::fetch(pg_pool.get_ref(), client_id)
         .await
         .map_err(|msg| JsonResponse::<models::Client>::build().internal_server_error(msg))?
         .ok_or_else(|| JsonResponse::<models::Client>::build().not_found("not found"))?;
 
+    if client.user_id != user.id {
+        return Err(JsonResponse::<models::Client>::build().bad_request("client is not the owner"));
+    }
+
+    update_client(pg_pool.get_ref(), client).await
+}
+
+#[tracing::instrument(name = "Admin update client.")]
+#[put("/{id}")]
+pub async fn admin_update_handler(
+    user: web::ReqData<Arc<models::User>>,
+    settings: web::Data<Settings>,
+    pg_pool: web::Data<PgPool>,
+    path: web::Path<(i32,)>,
+) -> Result<impl Responder> {
+    let client_id = path.0;
+    let mut client = db::client::fetch(pg_pool.get_ref(), client_id)
+        .await
+        .map_err(|msg| JsonResponse::<models::Client>::build().internal_server_error(msg))?
+        .ok_or_else(|| JsonResponse::<models::Client>::build().not_found("not found"))?;
+
+    update_client(pg_pool.get_ref(), client).await
+}
+
+async fn update_client(pg_pool: &PgPool, mut client: models::Client) -> Result<impl Responder> {
     if client.secret.is_none() {
         return Err(JsonResponse::<models::Client>::build().bad_request("client is not active"));
     }
 
-    client.secret = client::generate_secret(pg_pool.get_ref(), 255)
+    client.secret = client::generate_secret(pg_pool, 255)
         .await
         .map(|s| Some(s))
         .map_err(|msg| JsonResponse::<models::Client>::build().bad_request(msg))?;
 
-    db::client::update(pg_pool.get_ref(), client)
+    db::client::update(pg_pool, client)
         .await
         .map(|client| {
             JsonResponse::<models::Client>::build()
diff --git a/src/startup.rs b/src/startup.rs
index 7e3786dc..22d071f3 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -44,6 +44,8 @@ pub async fn run(
             .service(
                 web::scope("/admin/client")
                     .service(routes::client::admin_enable_handler)
+                    .service(routes::client::admin_update_handler)
+                    .service(routes::client::admin_disable_handler),
             )
             .service(
                 web::scope("/test").service(routes::test::deploy::handler),

From de85d4b694ba6569a28fde0e818e615bcbe9b0ca Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 2 Mar 2024 17:19:31 +0200
Subject: [PATCH 199/284] 30-access-policies removed
 src/routes/stack/service.rs

---
 src/routes/stack/service.rs | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 src/routes/stack/service.rs

diff --git a/src/routes/stack/service.rs b/src/routes/stack/service.rs
deleted file mode 100644
index e69de29b..00000000

From 618c30ea735c267a85d6173d63b203a1f02373ab Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 2 Mar 2024 17:39:46 +0200
Subject: [PATCH 200/284] 30-access-policies owner logic for POST /stack/:id

---
 src/routes/stack/update.rs | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/routes/stack/update.rs b/src/routes/stack/update.rs
index ef11d825..72a0b441 100644
--- a/src/routes/stack/update.rs
+++ b/src/routes/stack/update.rs
@@ -27,11 +27,15 @@ pub async fn update(
             None => Err(JsonResponse::<models::Stack>::build().not_found("Object not found")),
         })?;
 
+    if stack.user_id != user.id {
+        return Err(JsonResponse::<models::Client>::build().bad_request("client is not the owner"));
+    }
+
     let stack_name = form.custom.custom_stack_code.clone();
     tracing::debug!("form data: {:?}", form);
     let user_id = user.id.clone();
 
-    if let Err(errors) = form.validate() {
+    if let Err(errors) = form.validate() { 
         return Err(JsonResponse::<models::Stack>::build().form_error(errors.to_string()));
     }
 

From b599b98af9d5a7e5840239b270b3ff43846d4eb1 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 2 Mar 2024 17:59:49 +0200
Subject: [PATCH 201/284] 30-access-policies admin, GET /stack/:id

---
 src/routes/stack/get.rs | 19 +++++++++++++++++++
 src/startup.rs          |  4 ++++
 2 files changed, 23 insertions(+)

diff --git a/src/routes/stack/get.rs b/src/routes/stack/get.rs
index ccb9c9de..d6b2e27f 100644
--- a/src/routes/stack/get.rs
+++ b/src/routes/stack/get.rs
@@ -29,6 +29,25 @@ pub async fn item(
         })
 }
 
+#[tracing::instrument(name = "Get logged user stack.")]
+#[get("/{id}")]
+pub async fn admin_item(
+    user: web::ReqData<Arc<models::User>>,
+    path: web::Path<(i32,)>,
+    pg_pool: web::Data<PgPool>,
+) -> Result<impl Responder> {
+    /// Get stack apps of logged user only
+    let (id,) = path.into_inner();
+
+    db::stack::fetch(pg_pool.get_ref(), id)
+        .await
+        .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(err))
+        .and_then(|stack| match stack {
+            Some(stack) => Ok(JsonResponse::build().set_item(Some(stack)).ok("OK")),
+            None => Err(JsonResponse::<models::Stack>::build().not_found("not found")),
+        })
+}
+
 #[tracing::instrument(name = "Get user's stack list.")]
 #[get("/user/{id}")]
 pub async fn list(
diff --git a/src/startup.rs b/src/startup.rs
index 22d071f3..c59aff8e 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -69,6 +69,10 @@ pub async fn run(
                     .service(routes::stack::add::add)
                     .service(routes::stack::update::update),
             )
+            .service(
+                web::scope("/admin/stack")
+                    .service(routes::stack::get::admin_item)
+            )
             .app_data(pg_pool.clone())
             .app_data(mq_manager.clone())
             .app_data(settings.clone())

From 15b890b61436ce190a41f1a52bab6ad88e74a990 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sun, 3 Mar 2024 08:33:17 +0200
Subject: [PATCH 202/284] 30-access-policies GET /stack, GET
 /admin/stack/user/:id

---
 src/routes/stack/get.rs | 25 ++++++++++++++++++++-----
 src/startup.rs          |  1 +
 2 files changed, 21 insertions(+), 5 deletions(-)

diff --git a/src/routes/stack/get.rs b/src/routes/stack/get.rs
index d6b2e27f..132a6653 100644
--- a/src/routes/stack/get.rs
+++ b/src/routes/stack/get.rs
@@ -7,7 +7,22 @@ use std::convert::From;
 use std::sync::Arc;
 use tracing::Instrument;
 
-#[tracing::instrument(name = "Get logged user stack.")]
+#[tracing::instrument(name = "User get user's stack list.")]
+#[get("")]
+pub async fn list(
+    user: web::ReqData<Arc<models::User>>,
+    pg_pool: web::Data<PgPool>,
+) -> Result<impl Responder> {
+    /// This is admin endpoint, used by a client app, client app is confidential
+    /// it should return stacks by user id
+    /// in order to pass validation at external deployment service
+    db::stack::fetch_by_user(pg_pool.get_ref(), &user.id)
+        .await
+        .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(err))
+        .map(|stacks| JsonResponse::build().set_list(stacks).ok("OK"))
+}
+
+#[tracing::instrument(name = "User get logged user stack.")]
 #[get("/{id}")]
 pub async fn item(
     user: web::ReqData<Arc<models::User>>,
@@ -29,7 +44,7 @@ pub async fn item(
         })
 }
 
-#[tracing::instrument(name = "Get logged user stack.")]
+#[tracing::instrument(name = "Admin get logged user stack.")]
 #[get("/{id}")]
 pub async fn admin_item(
     user: web::ReqData<Arc<models::User>>,
@@ -48,10 +63,10 @@ pub async fn admin_item(
         })
 }
 
-#[tracing::instrument(name = "Get user's stack list.")]
+#[tracing::instrument(name = "User get user's stack list.")]
 #[get("/user/{id}")]
-pub async fn list(
-    user: web::ReqData<Arc<models::User>>,
+pub async fn admin_list(
+    admin: web::ReqData<Arc<models::User>>,
     path: web::Path<(String,)>,
     pg_pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
diff --git a/src/startup.rs b/src/startup.rs
index c59aff8e..b8052662 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -72,6 +72,7 @@ pub async fn run(
             .service(
                 web::scope("/admin/stack")
                     .service(routes::stack::get::admin_item)
+                    .service(routes::stack::get::admin_list)
             )
             .app_data(pg_pool.clone())
             .app_data(mq_manager.clone())

From 9f92af23c35300c69b5b7b1b27dc3355a23cfc87 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Mon, 4 Mar 2024 10:27:41 +0200
Subject: [PATCH 203/284] delete action for project/cloud/server, require
 review and optimization (duplicates etc)

---
 ...g_original_request_column_project.down.sql |  2 +
 ...ing_original_request_column_project.up.sql |  1 +
 src/db/cloud.rs                               | 36 +++++++++++++
 src/db/project.rs                             | 18 ++++---
 src/db/server.rs                              | 36 +++++++++++++
 src/forms/project/form.rs                     |  2 +-
 src/models/project.rs                         | 11 ++--
 src/routes/cloud/delete.rs                    | 51 ++++++++++++++++++
 src/routes/cloud/mod.rs                       |  2 +
 src/routes/project/add.rs                     | 21 ++++++--
 src/routes/project/delete.rs                  | 53 ++++++++++---------
 src/routes/server/delete.rs                   | 50 +++++++++++++++++
 src/routes/server/mod.rs                      |  2 +
 13 files changed, 243 insertions(+), 42 deletions(-)
 create mode 100644 migrations/20240302081015_creating_original_request_column_project.down.sql
 create mode 100644 migrations/20240302081015_creating_original_request_column_project.up.sql
 create mode 100644 src/routes/cloud/delete.rs
 create mode 100644 src/routes/server/delete.rs

diff --git a/migrations/20240302081015_creating_original_request_column_project.down.sql b/migrations/20240302081015_creating_original_request_column_project.down.sql
new file mode 100644
index 00000000..93549b57
--- /dev/null
+++ b/migrations/20240302081015_creating_original_request_column_project.down.sql
@@ -0,0 +1,2 @@
+-- Add down migration script here
+ALTER table project DROP COLUMN request_json;
diff --git a/migrations/20240302081015_creating_original_request_column_project.up.sql b/migrations/20240302081015_creating_original_request_column_project.up.sql
new file mode 100644
index 00000000..2c1ba74c
--- /dev/null
+++ b/migrations/20240302081015_creating_original_request_column_project.up.sql
@@ -0,0 +1 @@
+ALTER table project ADD COLUMN request_json JSON NOT NULL DEFAULT '{}';
\ No newline at end of file
diff --git a/src/db/cloud.rs b/src/db/cloud.rs
index 03854342..d1b332ff 100644
--- a/src/db/cloud.rs
+++ b/src/db/cloud.rs
@@ -118,3 +118,39 @@ pub async fn update(pool: &PgPool, mut cloud: models::Cloud) -> Result<models::C
             "".to_string()
         })
 }
+
+#[tracing::instrument(name = "Delete cloud of a user.")]
+pub async fn delete(pool: &PgPool, id: i32) -> Result<bool, String> {
+    tracing::info!("Delete cloud {}", id);
+    let mut tx = match pool.begin().await {
+        Ok(result) => result,
+        Err(err) => {
+            tracing::error!("Failed to begin transaction: {:?}", err);
+            return Err("".to_string());
+        }
+    };
+
+    let delete_query = " DELETE FROM cloud WHERE id = $1; ";
+
+    match sqlx::query(delete_query)
+        .bind(id)
+        .execute(&mut tx)
+        .await
+        .map_err(|err| {
+            println!("{:?}", err)
+        })
+    {
+        Ok(_) => {
+            tx.commit().await.map_err(|err| {
+                tracing::error!("Failed to commit transaction: {:?}", err);
+                false
+            });
+            Ok(true)
+        }
+        Err(err) => {
+            tx.rollback().await.map_err(|err| println!("{:?}", err));
+            Ok(false)
+        }
+    }
+
+}
diff --git a/src/db/project.rs b/src/db/project.rs
index 8104b061..210a718e 100644
--- a/src/db/project.rs
+++ b/src/db/project.rs
@@ -1,6 +1,5 @@
 use crate::models;
 use sqlx::PgPool;
-use sqlx::postgres::PgRow;
 use tracing::Instrument;
 
 pub async fn fetch(pool: &PgPool, id: i32) -> Result<Option<models::Project>, String> {
@@ -79,8 +78,8 @@ pub async fn insert(pool: &PgPool, mut project: models::Project) -> Result<model
     let query_span = tracing::info_span!("Saving new project into the database");
     sqlx::query!(
         r#"
-        INSERT INTO project (stack_id, user_id, name, body, created_at, updated_at, cloud_id)
-        VALUES ($1, $2, $3, $4, $5, $6, $7)
+        INSERT INTO project (stack_id, user_id, name, body, created_at, updated_at, cloud_id, request_json)
+        VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
         RETURNING id;
         "#,
         project.stack_id,
@@ -90,6 +89,7 @@ pub async fn insert(pool: &PgPool, mut project: models::Project) -> Result<model
         project.created_at,
         project.updated_at,
         project.cloud_id,
+        project.request_json,
     )
     .fetch_one(pool)
     .instrument(query_span)
@@ -116,7 +116,8 @@ pub async fn update(pool: &PgPool, mut project: models::Project) -> Result<model
             cloud_id=$4,
             name=$5,
             body=$6,
-            created_at=$7,
+            request_json=$7,
+            created_at=$8,
             updated_at=NOW() at time zone 'utc'
         WHERE id = $1
         RETURNING *
@@ -127,6 +128,7 @@ pub async fn update(pool: &PgPool, mut project: models::Project) -> Result<model
         project.cloud_id,
         project.name,
         project.body,
+        project.request_json,
         project.created_at,
     )
     .fetch_one(pool)
@@ -143,6 +145,7 @@ pub async fn update(pool: &PgPool, mut project: models::Project) -> Result<model
     })
 }
 
+#[tracing::instrument(name = "Delete user's project.")]
 pub async fn delete(pool: &PgPool, id: i32) -> Result<bool, String> {
     tracing::info!("Delete project {}", id);
     let mut tx = match pool.begin().await {
@@ -155,8 +158,8 @@ pub async fn delete(pool: &PgPool, id: i32) -> Result<bool, String> {
 
     // Combine delete queries into a single query
     let delete_query = "
-        DELETE FROM deployment WHERE project_id = $1;
-        DELETE FROM server WHERE project_id = $1;
+        --DELETE FROM deployment WHERE project_id = $1; // on delete cascade
+        --DELETE FROM server WHERE project_id = $1; // on delete cascade
         DELETE FROM project WHERE id = $1;
     ";
 
@@ -179,8 +182,7 @@ pub async fn delete(pool: &PgPool, id: i32) -> Result<bool, String> {
             tx.rollback().await.map_err(|err| println!("{:?}", err));
             Ok(false)
         }
+        // todo, when empty commit()
     }
-    // Ok(true)
-
 }
 
diff --git a/src/db/server.rs b/src/db/server.rs
index c471cf7e..10a0ea65 100644
--- a/src/db/server.rs
+++ b/src/db/server.rs
@@ -150,3 +150,39 @@ pub async fn update(pool: &PgPool, mut server: models::Server) -> Result<models:
             "".to_string()
         })
 }
+
+#[tracing::instrument(name = "Delete user's server.")]
+pub async fn delete(pool: &PgPool, id: i32) -> Result<bool, String> {
+    tracing::info!("Delete server {}", id);
+    let mut tx = match pool.begin().await {
+        Ok(result) => result,
+        Err(err) => {
+            tracing::error!("Failed to begin transaction: {:?}", err);
+            return Err("".to_string());
+        }
+    };
+
+    let delete_query = " DELETE FROM server WHERE id = $1; ";
+
+    match sqlx::query(delete_query)
+        .bind(id)
+        .execute(&mut tx)
+        .await
+        .map_err(|err| {
+            println!("{:?}", err)
+        })
+    {
+        Ok(_) => {
+            tx.commit().await.map_err(|err| {
+                tracing::error!("Failed to commit transaction: {:?}", err);
+                false
+            });
+            Ok(true)
+        }
+        Err(err) => {
+            tx.rollback().await.map_err(|err| println!("{:?}", err));
+            Ok(false)
+        }
+    }
+
+}
diff --git a/src/forms/project/form.rs b/src/forms/project/form.rs
index 4321f33b..d29233f9 100644
--- a/src/forms/project/form.rs
+++ b/src/forms/project/form.rs
@@ -25,7 +25,7 @@ pub struct ProjectForm {
     pub extended_features: Option<Vec<Value>>,
     pub subscriptions: Option<Vec<String>>,
     pub form_app: Option<Vec<String>>,
-    pub custom: forms::project::Custom,
+    pub custom: forms::project::Custom
 }
 
 impl TryFrom<&models::Project> for ProjectForm {
diff --git a/src/models/project.rs b/src/models/project.rs
index 06d29053..1f33df8b 100644
--- a/src/models/project.rs
+++ b/src/models/project.rs
@@ -2,7 +2,6 @@ use chrono::{DateTime, Utc};
 use serde::{Deserialize, Serialize};
 use serde_json::Value;
 use uuid::Uuid;
-use crate::db;
 
 #[derive(Debug, Serialize, Deserialize, Clone)]
 pub struct Project {
@@ -13,19 +12,21 @@ pub struct Project {
     pub name: String,
     // pub body: sqlx::types::Json<String>,
     pub body: Value, //json type
+    pub request_json: Value,
     pub created_at: DateTime<Utc>,
     pub updated_at: DateTime<Utc>,
 }
 
 impl Project {
-    pub fn new(user_id: String, name: String, body: Value) -> Self {
+    pub fn new(user_id: String, name: String, body: Value, request_json: Value) -> Self {
         Self {
             id: 0,
             stack_id: Uuid::new_v4(),
             cloud_id: None,
-            user_id: user_id,
-            name: name,
-            body: body,
+            user_id,
+            name,
+            body,
+            request_json,
             created_at: Utc::now(),
             updated_at: Utc::now(),
         }
diff --git a/src/routes/cloud/delete.rs b/src/routes/cloud/delete.rs
new file mode 100644
index 00000000..b69aec4e
--- /dev/null
+++ b/src/routes/cloud/delete.rs
@@ -0,0 +1,51 @@
+use crate::helpers::JsonResponse;
+use crate::models;
+use actix_web::{delete, web, Responder, Result};
+use sqlx::PgPool;
+use std::sync::Arc;
+use futures_util::FutureExt;
+use tracing::Instrument;
+use crate::db;
+use crate::models::Cloud;
+
+#[tracing::instrument(name = "Delete cloud record of a user.")]
+#[delete("/{id}")]
+pub async fn item(
+    user: web::ReqData<Arc<models::User>>,
+    path: web::Path<(i32,)>,
+    pg_pool: web::Data<PgPool>,
+) -> Result<impl Responder> {
+    /// Get cloud apps of logged user only
+    let (id,) = path.into_inner();
+
+    let cloud = db::cloud::fetch(pg_pool.get_ref(), id)
+        .await
+        .map_err(|err| JsonResponse::<models::Cloud>::build().internal_server_error(err))
+        .and_then(|cloud| {
+            match cloud {
+                Some(cloud) if cloud.user_id != user.id => {
+                    Err(JsonResponse::<models::Cloud>::build().bad_request("Delete is forbidden"))
+                }
+                Some(cloud) => {
+                    Ok(cloud)
+                },
+                None => Err(JsonResponse::<models::Cloud>::build().not_found(""))
+            }
+        })?;
+
+    db::cloud::delete(pg_pool.get_ref(), cloud.id)
+        .await
+        .map_err(|err| JsonResponse::<Cloud>::build().internal_server_error(err))
+        .and_then(|result| {
+            match result
+            {
+                true => {
+                    Ok(JsonResponse::<Cloud>::build().ok("Deleted"))
+                }
+                _ => {
+                    Err(JsonResponse::<Cloud>::build().bad_request("Could not delete"))
+                }
+            }
+        })
+
+}
diff --git a/src/routes/cloud/mod.rs b/src/routes/cloud/mod.rs
index a489a06d..bc351583 100644
--- a/src/routes/cloud/mod.rs
+++ b/src/routes/cloud/mod.rs
@@ -1,7 +1,9 @@
 pub mod add;
 pub mod get;
 pub mod update;
+mod delete;
 
 pub use add::*;
 pub use get::*;
 pub use update::*;
+pub use delete::*;
diff --git a/src/routes/project/add.rs b/src/routes/project/add.rs
index 05fb6cf0..963e07e0 100644
--- a/src/routes/project/add.rs
+++ b/src/routes/project/add.rs
@@ -13,6 +13,7 @@ use serde_valid::Validate;
 use sqlx::PgPool;
 use std::str;
 use std::sync::Arc;
+use std::str::FromStr;
 
 #[tracing::instrument(name = "Add project.")]
 #[post("")]
@@ -22,14 +23,27 @@ pub async fn item(
     pg_pool: Data<PgPool>,
 ) -> Result<impl Responder> {
     // @todo ACL
-    let form = body_into_form(body).await?;
+    let form = body_into_form(body.clone()).await?;
     let project_name = form.custom.custom_stack_code.clone();
 
+    //let request_json = Some(serde_json::Value::from_str(r#"{"somefield": "somevalue"}"#).unwrap());
+    // let request_json = form.request_json.clone();
+    let body_bytes = actix_web::body::to_bytes(body).await.unwrap();
+    let body_str = str::from_utf8(&body_bytes)
+        .map_err(|err| JsonResponse::<forms::project::ProjectForm>::build().internal_server_error(err.to_string()))?;
+    let request_json = Value::from_str(body_str).unwrap();
+    tracing::debug!("Request json: {:?}", request_json);
+
     let body: Value = serde_json::to_value::<forms::project::ProjectForm>(form)
         .or(serde_json::to_value::<forms::project::ProjectForm>(forms::project::ProjectForm::default()))
         .unwrap();
 
-    let project = models::Project::new(user.id.clone(), project_name, body);
+    let project = models::Project::new(
+        user.id.clone(),
+        project_name,
+        body,
+        request_json
+    );
     db::project::insert(pg_pool.get_ref(), project)
         .await
         .map(|project| JsonResponse::build().set_item(project).ok("Ok"))
@@ -49,7 +63,7 @@ async fn body_into_form(body: Bytes) -> Result<forms::project::ProjectForm, Erro
             let msg = format!("{}:{:?}", err.path().to_string(), err);
             JsonResponse::<forms::project::ProjectForm>::build().bad_request(msg)
         })
-        .and_then(|form: forms::project::ProjectForm| {
+        .and_then(|mut form: forms::project::ProjectForm| {
             if !form.validate().is_ok() {
                 let errors = form.validate().unwrap_err().to_string();
                 let err_msg = format!("Invalid data received {:?}", &errors);
@@ -61,3 +75,4 @@ async fn body_into_form(body: Bytes) -> Result<forms::project::ProjectForm, Erro
             Ok(form)
         })
 }
+
diff --git a/src/routes/project/delete.rs b/src/routes/project/delete.rs
index 16cc579a..c0e1b269 100644
--- a/src/routes/project/delete.rs
+++ b/src/routes/project/delete.rs
@@ -3,6 +3,7 @@ use crate::models;
 use actix_web::{delete, web, Responder, Result};
 use sqlx::PgPool;
 use std::sync::Arc;
+use futures_util::FutureExt;
 use tracing::Instrument;
 use crate::db;
 use crate::models::Project;
@@ -17,32 +18,34 @@ pub async fn item(
     /// Get project apps of logged user only
     let (id,) = path.into_inner();
 
-    let project = db::project::fetch(pg_pool.get_ref(), id).await
+    let project = db::project::fetch(pg_pool.get_ref(), id)
+        .await
         .map_err(|err| JsonResponse::<models::Project>::build().internal_server_error(err))
-        .map(|project| { project }).unwrap();
-    //
-    //         match project {
-    //             Some(project) if project.user_id != user.id => {
-    //                 Err(JsonResponse::<models::Project>::build().not_found("not found"))
-    //             }
-    //             Some(project) => {
-    //
-    //                     match db::project::delete(pg_pool.get_ref(), id)
-    //                         .await
-    //                         .map_err(|e| println!("{:?}", e))
-    //                     {
-    //                         Ok(true) => {
-    //                             Ok(JsonResponse::build().set_item(Some(project)).ok("Deleted"))
-    //                         }
-    //                         _ => {
-    //                             Err(JsonResponse::<models::Project>::build().bad_request("Could not delete"))
-    //                         }
-    //                     }
-    //             },
-    //             None => Err(JsonResponse::<models::Project>::build().not_found("not found")),
-    //         }
-    //     })
+        .and_then(|project| {
+            match project {
+                Some(project) if project.user_id != user.id => {
+                    Err(JsonResponse::<models::Project>::build().bad_request("Delete is forbidden"))
+                }
+                Some(project) => {
+                    Ok(project)
+                },
+                None => Err(JsonResponse::<models::Project>::build().not_found(""))
+            }
+        })?;
 
+    db::project::delete(pg_pool.get_ref(), project.id)
+        .await
+        .map_err(|err| JsonResponse::<Project>::build().internal_server_error(err))
+        .and_then(|result| {
+            match result
+            {
+                true => {
+                    Ok(JsonResponse::<Project>::build().ok("Deleted"))
+                }
+                _ => {
+                    Err(JsonResponse::<Project>::build().bad_request("Could not delete"))
+                }
+            }
+        })
 
-    Ok(JsonResponse::<Project>::build().ok("Deleted"))
 }
diff --git a/src/routes/server/delete.rs b/src/routes/server/delete.rs
new file mode 100644
index 00000000..cd9a8240
--- /dev/null
+++ b/src/routes/server/delete.rs
@@ -0,0 +1,50 @@
+use crate::helpers::JsonResponse;
+use crate::models;
+use actix_web::{delete, web, Responder, Result};
+use sqlx::PgPool;
+use std::sync::Arc;
+use tracing::Instrument;
+use crate::db;
+use crate::models::Server;
+
+#[tracing::instrument(name = "Delete user's server.")]
+#[delete("/{id}")]
+pub async fn item(
+    user: web::ReqData<Arc<models::User>>,
+    path: web::Path<(i32,)>,
+    pg_pool: web::Data<PgPool>,
+) -> Result<impl Responder> {
+    /// Get server apps of logged user only
+    let (id,) = path.into_inner();
+
+    let server = db::server::fetch(pg_pool.get_ref(), id)
+        .await
+        .map_err(|err| JsonResponse::<models::Server>::build().internal_server_error(err))
+        .and_then(|server| {
+            match server {
+                Some(server) if server.user_id != user.id => {
+                    Err(JsonResponse::<models::Server>::build().bad_request("Delete is forbidden"))
+                }
+                Some(server) => {
+                    Ok(server)
+                },
+                None => Err(JsonResponse::<models::Server>::build().not_found(""))
+            }
+        })?;
+
+    db::server::delete(pg_pool.get_ref(), server.id)
+        .await
+        .map_err(|err| JsonResponse::<Server>::build().internal_server_error(err))
+        .and_then(|result| {
+            match result
+            {
+                true => {
+                    Ok(JsonResponse::<Server>::build().ok("Item deleted"))
+                }
+                _ => {
+                    Err(JsonResponse::<Server>::build().bad_request("Could not delete"))
+                }
+            }
+        })
+
+}
diff --git a/src/routes/server/mod.rs b/src/routes/server/mod.rs
index d3122a38..5f3597b0 100644
--- a/src/routes/server/mod.rs
+++ b/src/routes/server/mod.rs
@@ -1,5 +1,7 @@
 pub mod add;
 pub(crate) mod get;
+mod delete;
 
 pub use add::*;
 pub use get::*;
+pub use delete::*;

From aca9fdc0880c56ec884df21addc5f43012a56123 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Mon, 4 Mar 2024 13:11:50 +0200
Subject: [PATCH 204/284] form fields fix for server/cloud

---
 src/db/cloud.rs                    |  4 +++-
 src/forms/cloud.rs                 |  7 ++++---
 src/forms/server.rs                |  9 +++++----
 src/routes/cloud/mod.rs            |  2 +-
 src/routes/server/mod.rs           |  2 +-
 src/startup.rs                     |  2 ++
 tests/mock_data/cloud.json         |  8 ++++++++
 tests/mock_data/project.json       |  7 +++++++
 tests/mock_data/server-update.json | 14 ++++++++++++++
 tests/mock_data/server.json        | 13 +++++++++++++
 10 files changed, 58 insertions(+), 10 deletions(-)
 create mode 100644 tests/mock_data/cloud.json
 create mode 100644 tests/mock_data/project.json
 create mode 100644 tests/mock_data/server-update.json
 create mode 100644 tests/mock_data/server.json

diff --git a/src/db/cloud.rs b/src/db/cloud.rs
index d1b332ff..51c3f243 100644
--- a/src/db/cloud.rs
+++ b/src/db/cloud.rs
@@ -92,7 +92,8 @@ pub async fn update(pool: &PgPool, mut cloud: models::Cloud) -> Result<models::C
             cloud_token=$4,
             cloud_key=$5,
             cloud_secret=$6,
-            created_at=$7,
+            save_token=$7,
+            created_at=$8,
             updated_at=NOW() at time zone 'utc'
         WHERE id = $1
         RETURNING *
@@ -103,6 +104,7 @@ pub async fn update(pool: &PgPool, mut cloud: models::Cloud) -> Result<models::C
         cloud.cloud_token,
         cloud.cloud_key,
         cloud.cloud_secret,
+        cloud.save_token,
         cloud.created_at,
     )
         .fetch_one(pool)
diff --git a/src/forms/cloud.rs b/src/forms/cloud.rs
index e0fafd9a..cbde8b07 100644
--- a/src/forms/cloud.rs
+++ b/src/forms/cloud.rs
@@ -20,9 +20,10 @@ impl Into<models::Cloud> for Cloud {
         let mut cloud = models::Cloud::default();
         cloud.user_id = self.user_id;
         cloud.provider = self.provider;
-        cloud.cloud_token = Some(String::from(""));
-        cloud.cloud_key = Some(String::from(""));
-        cloud.cloud_secret = Some(String::from(""));
+        cloud.cloud_token = self.cloud_token;
+        cloud.cloud_key = self.cloud_key;
+        cloud.cloud_secret = self.cloud_secret;
+        cloud.save_token = self.save_token;
         cloud.created_at = Utc::now();
         cloud.updated_at = Utc::now();
 
diff --git a/src/forms/server.rs b/src/forms/server.rs
index edfe8270..cc69c6d1 100644
--- a/src/forms/server.rs
+++ b/src/forms/server.rs
@@ -21,10 +21,11 @@ impl Into<models::Server> for Server {
         server.user_id = self.user_id;
         server.cloud_id = self.cloud_id;
         server.project_id = self.project_id;
-        server.region = String::from("");
-        server.zone = Some(String::from(""));
-        server.server = String::from("");
-        server.os = String::from("");
+        server.disk_type = self.disk_type;
+        server.region = self.region;
+        server.server = self.server;
+        server.zone = self.zone;
+        server.os = self.os;
         server.created_at = Utc::now();
         server.updated_at = Utc::now();
 
diff --git a/src/routes/cloud/mod.rs b/src/routes/cloud/mod.rs
index bc351583..bc45c2a7 100644
--- a/src/routes/cloud/mod.rs
+++ b/src/routes/cloud/mod.rs
@@ -1,7 +1,7 @@
 pub mod add;
 pub mod get;
 pub mod update;
-mod delete;
+pub(crate) mod delete;
 
 pub use add::*;
 pub use get::*;
diff --git a/src/routes/server/mod.rs b/src/routes/server/mod.rs
index 5f3597b0..551560fb 100644
--- a/src/routes/server/mod.rs
+++ b/src/routes/server/mod.rs
@@ -1,6 +1,6 @@
 pub mod add;
 pub(crate) mod get;
-mod delete;
+pub(crate) mod delete;
 
 pub use add::*;
 pub use get::*;
diff --git a/src/startup.rs b/src/startup.rs
index 3c8165c5..bcd1428b 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -89,6 +89,7 @@ pub async fn run(
                     .service(crate::routes::cloud::get::item)
                     .service(crate::routes::cloud::get::list)
                     .service(crate::routes::cloud::add::add)
+                    .service(crate::routes::cloud::delete::item),
             )
             .service(
                 web::scope("/server")
@@ -99,6 +100,7 @@ pub async fn run(
                     .service(crate::routes::server::get::item)
                     .service(crate::routes::server::get::list)
                     .service(crate::routes::server::add::add)
+                    .service(crate::routes::server::delete::item),
             )
             .app_data(pg_pool.clone())
             .app_data(mq_manager.clone())
diff --git a/tests/mock_data/cloud.json b/tests/mock_data/cloud.json
new file mode 100644
index 00000000..9e680c90
--- /dev/null
+++ b/tests/mock_data/cloud.json
@@ -0,0 +1,8 @@
+{
+  "user_id": "hy181TZa4DaabUZWklsrxw",
+  "provider": "htz",
+  "cloud_token": "cloud_token_here",
+  "cloud_key": "cloud_token_here",
+  "cloud_secret": "cloud_secret_here",
+  "save_token": true
+}
diff --git a/tests/mock_data/project.json b/tests/mock_data/project.json
new file mode 100644
index 00000000..9ff62aa5
--- /dev/null
+++ b/tests/mock_data/project.json
@@ -0,0 +1,7 @@
+{
+  "stack_id": "9239ea1d-8306-4493-aae1-fcc00de76241",
+  "user_id": "hy181TZa4DaabUZWklsrxw",
+  "name": "sample",
+  "body": "{}",
+  "cloud_id": 1
+}
\ No newline at end of file
diff --git a/tests/mock_data/server-update.json b/tests/mock_data/server-update.json
new file mode 100644
index 00000000..4ad21734
--- /dev/null
+++ b/tests/mock_data/server-update.json
@@ -0,0 +1,14 @@
+{
+  "id": 1,
+  "user_id": "hy181TZa4DaabUZWklsrxw",
+  "cloud_id": 1,
+  "region": "fra-1",
+  "zone": "a",
+  "server": "server-1",
+  "os": "3408230498203948234",
+  "disk_type": "samples",
+  "created_at": "",
+  "updated_at": "",
+  "project_id": 1
+}
+
diff --git a/tests/mock_data/server.json b/tests/mock_data/server.json
new file mode 100644
index 00000000..cec56c91
--- /dev/null
+++ b/tests/mock_data/server.json
@@ -0,0 +1,13 @@
+{
+  "user_id": "hy181TZa4DaabUZWklsrxw",
+  "cloud_id": 3,
+  "region": "fra-1",
+  "zone": "a",
+  "server": "server-1",
+  "os": "3408230498203948234",
+  "disk_type": "samples",
+  "created_at": "",
+  "updated_at": "",
+  "project_id":4
+}
+

From 98b5a7dda7a4247101dcb7072c6d34a1afe71fb3 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Mon, 4 Mar 2024 17:24:01 +0200
Subject: [PATCH 205/284] 30-access-policies /stack/:id/deploy

---
 src/routes/client/disable.rs | 14 ++++++++------
 src/routes/stack/deploy.rs   |  1 +
 src/routes/stack/update.rs   |  5 +----
 3 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/src/routes/client/disable.rs b/src/routes/client/disable.rs
index 892d3886..08a310cc 100644
--- a/src/routes/client/disable.rs
+++ b/src/routes/client/disable.rs
@@ -18,12 +18,14 @@ pub async fn disable_handler(
     let client_id = path.0;
     let mut client = db::client::fetch(pg_pool.get_ref(), client_id)
         .await
-        .map_err(|msg| JsonResponse::<models::Client>::build().internal_server_error(msg))?
-        .ok_or_else(|| JsonResponse::<models::Client>::build().not_found("not found"))?;
-
-    if client.user_id != user.id {
-        return Err(JsonResponse::<models::Client>::build().bad_request("client is not the owner"));
-    }
+        .map_err(|msg| JsonResponse::<models::Client>::build().internal_server_error(msg))
+        .and_then( |client| {
+            match client {
+                Some(client) if client.user_id != user.id => Err(JsonResponse::<models::Client>::build().bad_request("client is not the owner")),
+                Some(client) => Ok(client),
+                None => Err(JsonResponse::<models::Client>::build().not_found("not found"))
+            }
+        })?;
 
     disable_client(pg_pool.get_ref(), client).await
 }
diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs
index a2bf8693..2120c16d 100644
--- a/src/routes/stack/deploy.rs
+++ b/src/routes/stack/deploy.rs
@@ -25,6 +25,7 @@ pub async fn add(
         .await
         .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(err))
         .and_then(|stack| match stack {
+            Some(stack) if stack.user_id != user.id => Err(JsonResponse::<models::Client>::build().bad_request("client is not the owner")),
             Some(stack) => Ok(stack),
             None => Err(JsonResponse::<models::Stack>::build().not_found("not found")),
         })?;
diff --git a/src/routes/stack/update.rs b/src/routes/stack/update.rs
index 72a0b441..c2eae717 100644
--- a/src/routes/stack/update.rs
+++ b/src/routes/stack/update.rs
@@ -23,14 +23,11 @@ pub async fn update(
         .await
         .map_err(|err| JsonResponse::<models::Stack>::build().internal_server_error(err))
         .and_then(|stack| match stack {
+            Some(stack) if stack.user_id != user.id => Err(JsonResponse::<models::Client>::build().bad_request("client is not the owner")),
             Some(stack) => Ok(stack),
             None => Err(JsonResponse::<models::Stack>::build().not_found("Object not found")),
         })?;
 
-    if stack.user_id != user.id {
-        return Err(JsonResponse::<models::Client>::build().bad_request("client is not the owner"));
-    }
-
     let stack_name = form.custom.custom_stack_code.clone();
     tracing::debug!("form data: {:?}", form);
     let user_id = user.id.clone();

From 4c152e9ebde4268543d4ae252de513761a3d91a5 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Mon, 4 Mar 2024 18:15:09 +0200
Subject: [PATCH 206/284] cloud update testing

---
 src/forms/cloud.rs                         |  2 +-
 src/forms/server.rs                        |  2 +-
 src/routes/cloud/update.rs                 | 55 ++++++++++++++++++++++
 src/routes/project/add.rs                  |  1 +
 src/routes/project/update.rs               | 13 +++--
 src/routes/server/add.rs                   | 40 +++++++++++++++-
 src/startup.rs                             |  2 +
 tests/mock_data/cloud-update.json          |  8 ++++
 tests/mock_data/server-update-invalid.json | 14 ++++++
 9 files changed, 126 insertions(+), 11 deletions(-)
 create mode 100644 tests/mock_data/cloud-update.json
 create mode 100644 tests/mock_data/server-update-invalid.json

diff --git a/src/forms/cloud.rs b/src/forms/cloud.rs
index cbde8b07..357edd20 100644
--- a/src/forms/cloud.rs
+++ b/src/forms/cloud.rs
@@ -24,7 +24,7 @@ impl Into<models::Cloud> for Cloud {
         cloud.cloud_key = self.cloud_key;
         cloud.cloud_secret = self.cloud_secret;
         cloud.save_token = self.save_token;
-        cloud.created_at = Utc::now();
+        // cloud.created_at = Utc::now();
         cloud.updated_at = Utc::now();
 
         cloud
diff --git a/src/forms/server.rs b/src/forms/server.rs
index cc69c6d1..fac003f5 100644
--- a/src/forms/server.rs
+++ b/src/forms/server.rs
@@ -2,7 +2,7 @@ use crate::models;
 use serde::{Deserialize, Serialize};
 use serde_valid::Validate;
 use chrono::{DateTime, Utc};
-
+use crate::db;
 #[derive(Serialize, Deserialize, Debug, Validate)]
 pub struct Server {
     pub user_id: String,
diff --git a/src/routes/cloud/update.rs b/src/routes/cloud/update.rs
index e69de29b..d4fd2387 100644
--- a/src/routes/cloud/update.rs
+++ b/src/routes/cloud/update.rs
@@ -0,0 +1,55 @@
+use crate::forms;
+use crate::helpers::JsonResponse;
+use crate::models;
+use crate::db;
+use actix_web::{web, web::Data, Responder, Result, post, put};
+use serde_valid::Validate;
+use sqlx::PgPool;
+use std::sync::Arc;
+use tracing::Instrument;
+
+#[tracing::instrument(name = "Update cloud.")]
+#[put("/{id}")]
+pub async fn item(
+    path: web::Path<(i32,)>,
+    form: web::Json<forms::cloud::Cloud>,
+    user: web::ReqData<Arc<models::User>>,
+    pg_pool: Data<PgPool>,
+) -> Result<impl Responder> {
+
+    let id = path.0;
+    let mut cloud_row = db::cloud::fetch(pg_pool.get_ref(), id)
+        .await
+        .map_err(|err| JsonResponse::<models::Cloud>::build().internal_server_error(err))
+        .and_then(|cloud| match cloud {
+            Some(cloud) if cloud.user_id != user.id => {
+                Err(JsonResponse::<models::Project>::build().bad_request("Cloud not found"))
+            }
+            Some(cloud) => Ok(cloud),
+            None => Err(JsonResponse::<models::Cloud>::build().not_found("Cloud not found")),
+        })?;
+
+    if let Err(errors) = form.validate() {
+        return Err(JsonResponse::<models::Cloud>::build().form_error(errors.to_string()));
+    }
+
+    let mut cloud:models::Cloud = form.into_inner().into();
+    cloud.id = cloud_row.id;
+    cloud.user_id = user.id.clone();
+    // exclude
+    // cloud.created_at
+
+    tracing::debug!("Updating cloud {:?}", cloud);
+
+    db::cloud::update(pg_pool.get_ref(), cloud)
+        .await
+        .map(|cloud| {
+            JsonResponse::<models::Cloud>::build()
+                .set_item(cloud)
+                .ok("success")
+        })
+        .map_err(|err| {
+            tracing::error!("Failed to execute query: {:?}", err);
+            JsonResponse::<models::Cloud>::build().internal_server_error("Could not update")
+        })
+}
diff --git a/src/routes/project/add.rs b/src/routes/project/add.rs
index 963e07e0..729285c2 100644
--- a/src/routes/project/add.rs
+++ b/src/routes/project/add.rs
@@ -44,6 +44,7 @@ pub async fn item(
         body,
         request_json
     );
+
     db::project::insert(pg_pool.get_ref(), project)
         .await
         .map(|project| JsonResponse::build().set_item(project).ok("Ok"))
diff --git a/src/routes/project/update.rs b/src/routes/project/update.rs
index e20d9090..f59d45a6 100644
--- a/src/routes/project/update.rs
+++ b/src/routes/project/update.rs
@@ -23,18 +23,19 @@ pub async fn item(
         .await
         .map_err(|err| JsonResponse::<models::Project>::build().internal_server_error(err))
         .and_then(|project| match project {
+            Some(project) if project.user_id != user.id => {
+                Err(JsonResponse::<models::Project>::build().bad_request("Project not found"))
+            }
             Some(project) => Ok(project),
-            None => Err(JsonResponse::<models::Project>::build().not_found("Object not found")),
+            None => Err(JsonResponse::<models::Project>::build().not_found("Project not found")),
         })?;
 
-    let project_name = form.custom.custom_stack_code.clone();
     tracing::debug!("form data: {:?}", form);
-    let user_id = user.id.clone();
-
     if let Err(errors) = form.validate() {
         return Err(JsonResponse::<models::Project>::build().form_error(errors.to_string()));
     }
 
+    let project_name = form.custom.custom_stack_code.clone();
     let form_inner = form.into_inner();
 
     if !form_inner.is_readable_docker_image().await.is_ok() {
@@ -44,10 +45,8 @@ pub async fn item(
     let body: Value = serde_json::to_value::<forms::project::ProjectForm>(form_inner)
         .map_err(|err| 
             JsonResponse::<models::Project>::build().bad_request(format!("{err}"))
-        )?; 
+        )?;
 
-    project.stack_id = Uuid::new_v4();
-    project.user_id = user_id;
     project.name = project_name;
     project.body = body;
 
diff --git a/src/routes/server/add.rs b/src/routes/server/add.rs
index 278f4e9a..e625df58 100644
--- a/src/routes/server/add.rs
+++ b/src/routes/server/add.rs
@@ -29,6 +29,35 @@ pub async fn add(
         return Err(JsonResponse::<models::Project>::build().form_error(errors));
     }
 
+    db::cloud::fetch(pg_pool.get_ref(), form.cloud_id)
+        .await
+        .map_err(|err| JsonResponse::<models::Cloud>::build().internal_server_error(err))
+        .and_then(|cloud| {
+            match cloud {
+                Some(cloud) if cloud.user_id != user.id => {
+                    Err(JsonResponse::<models::Cloud>::build().bad_request("Cloud not found"))
+                }
+                Some(cloud) => {
+                    Ok(cloud)
+                },
+                None => Err(JsonResponse::<models::Cloud>::build().not_found("Cloud not found"))
+            }
+        })?;
+
+    db::project::fetch(pg_pool.get_ref(), form.project_id)
+        .await
+        .map_err(|_err| JsonResponse::<models::Server>::build()
+            .bad_request("Invalid project"))
+        .and_then(|project| {
+            match project {
+                Some(project) if project.user_id != user.id => {
+                    Err(JsonResponse::<models::Project>::build().bad_request("Project not found"))
+                }
+                Some(project) => { Ok(project) },
+                None => Err(JsonResponse::<models::Server>::build().not_found("Project not found"))
+            }
+        })?;
+
     let mut server: models::Server = form.into_inner().into();
     server.user_id = user.id.clone();
 
@@ -37,6 +66,13 @@ pub async fn add(
         .map(|server| JsonResponse::build()
             .set_item(server)
             .ok("success"))
-        .map_err(|_err| JsonResponse::<models::Server>::build()
-            .internal_server_error("Failed to insert"))
+        .map_err(|err|
+            match err {
+                // sqlx::error::ErrorKind::ForeignKeyViolation => {
+                //     return JsonResponse::<models::Server>::build().bad_request("Failed to insert");
+                // }
+                _ => {
+                    return JsonResponse::<models::Server>::build().internal_server_error("Failed to insert");
+                }
+            })
 }
diff --git a/src/startup.rs b/src/startup.rs
index bcd1428b..06ada2a0 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -89,6 +89,7 @@ pub async fn run(
                     .service(crate::routes::cloud::get::item)
                     .service(crate::routes::cloud::get::list)
                     .service(crate::routes::cloud::add::add)
+                    .service(crate::routes::cloud::update::item)
                     .service(crate::routes::cloud::delete::item),
             )
             .service(
@@ -100,6 +101,7 @@ pub async fn run(
                     .service(crate::routes::server::get::item)
                     .service(crate::routes::server::get::list)
                     .service(crate::routes::server::add::add)
+                    // .service(crate::routes::server::update::item)
                     .service(crate::routes::server::delete::item),
             )
             .app_data(pg_pool.clone())
diff --git a/tests/mock_data/cloud-update.json b/tests/mock_data/cloud-update.json
new file mode 100644
index 00000000..d436baa1
--- /dev/null
+++ b/tests/mock_data/cloud-update.json
@@ -0,0 +1,8 @@
+{
+  "user_id": "hy181TZa4DaabUZWklsrxw",
+  "provider": "htz",
+  "cloud_token": "cloud_token_updates",
+  "cloud_key": "cloud_token_updates",
+  "cloud_secret": "cloud_secret_updates",
+  "save_token": false
+}
diff --git a/tests/mock_data/server-update-invalid.json b/tests/mock_data/server-update-invalid.json
new file mode 100644
index 00000000..51103450
--- /dev/null
+++ b/tests/mock_data/server-update-invalid.json
@@ -0,0 +1,14 @@
+{
+  "id": 1,
+  "user_id": "hy181TZa4DaabUZWklsrxw",
+  "cloud_id": 100000,
+  "region": "fra-1",
+  "zone": "a",
+  "server": "server-1",
+  "os": "3408230498203948234",
+  "disk_type": "samples",
+  "created_at": "",
+  "updated_at": "",
+  "project_id": 100000
+}
+

From 4cc54713087ed64479dac82715d1ed1480ed48e5 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Wed, 6 Mar 2024 12:19:53 +0200
Subject: [PATCH 207/284] project update fix, request_json updates,
 body_into_form moved to form

---
 src/db/client.rs             |  2 +-
 src/db/deployment.rs         |  2 +-
 src/db/project.rs            |  2 +-
 src/forms/cloud.rs           |  2 +-
 src/forms/project/form.rs    | 28 ++++++++++++++++++++++++++--
 src/forms/project/mod.rs     |  2 +-
 src/forms/rating.rs          |  2 +-
 src/routes/project/add.rs    | 32 ++------------------------------
 src/routes/project/update.rs | 32 ++++++++++++++++++++++----------
 src/routes/server/add.rs     |  3 ++-
 10 files changed, 58 insertions(+), 49 deletions(-)

diff --git a/src/db/client.rs b/src/db/client.rs
index b8307a72..8f13d9af 100644
--- a/src/db/client.rs
+++ b/src/db/client.rs
@@ -19,7 +19,7 @@ pub async fn update(pool: &PgPool, client: models::Client) -> Result<models::Cli
     .instrument(query_span)
     .await
     .map(|_|{
-        tracing::info!("Client {} have been saved to database", client.id);
+        tracing::info!("Client {} has been saved to the database", client.id);
         client
     })
     .map_err(|err| {
diff --git a/src/db/deployment.rs b/src/db/deployment.rs
index 1a22f02d..1bfb3b39 100644
--- a/src/db/deployment.rs
+++ b/src/db/deployment.rs
@@ -57,7 +57,7 @@ pub async fn insert(pool: &PgPool, mut deployment: models::Deployment) -> Result
 //         .instrument(query_span)
 //         .await
 //         .map(|result|{
-//             tracing::info!("Deployment {} have been saved to database", deployment.id);
+//             tracing::info!("Deployment {} has been saved to database", deployment.id);
 //             deployment.updated_at = result.updated_at;
 //             deployment
 //         })
diff --git a/src/db/project.rs b/src/db/project.rs
index 210a718e..fdeb6639 100644
--- a/src/db/project.rs
+++ b/src/db/project.rs
@@ -135,7 +135,7 @@ pub async fn update(pool: &PgPool, mut project: models::Project) -> Result<model
     .instrument(query_span)
     .await
     .map(|result|{
-        tracing::info!("Project {} have been saved to database", project.id);
+        tracing::info!("Project {} has been saved to database", project.id);
         project.updated_at = result.updated_at;
         project
     })
diff --git a/src/forms/cloud.rs b/src/forms/cloud.rs
index 357edd20..cbde8b07 100644
--- a/src/forms/cloud.rs
+++ b/src/forms/cloud.rs
@@ -24,7 +24,7 @@ impl Into<models::Cloud> for Cloud {
         cloud.cloud_key = self.cloud_key;
         cloud.cloud_secret = self.cloud_secret;
         cloud.save_token = self.save_token;
-        // cloud.created_at = Utc::now();
+        cloud.created_at = Utc::now();
         cloud.updated_at = Utc::now();
 
         cloud
diff --git a/src/forms/project/form.rs b/src/forms/project/form.rs
index d29233f9..73f0eb23 100644
--- a/src/forms/project/form.rs
+++ b/src/forms/project/form.rs
@@ -1,10 +1,12 @@
 use serde::{Deserialize, Serialize};
 use serde_json::Value;
 use serde_valid::Validate;
-use std::collections::HashMap;
-use std::fmt;
+use actix_web::Error;
+use actix_web::web::Bytes;
 use crate::models;
 use crate::forms;
+use crate::helpers::JsonResponse;
+use std::str;
 
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
@@ -67,3 +69,25 @@ impl ProjectForm {
     }
 }
 
+pub(crate) async fn body_into_form(body: Bytes) -> actix_web::Result<forms::project::ProjectForm, Error> {
+    let body_bytes = actix_web::body::to_bytes(body).await.unwrap();
+    let body_str = str::from_utf8(&body_bytes)
+        .map_err(|err| JsonResponse::<forms::project::ProjectForm>::build().internal_server_error(err.to_string()))?;
+    let deserializer = &mut serde_json::Deserializer::from_str(body_str);
+    serde_path_to_error::deserialize(deserializer)
+        .map_err(|err| {
+            let msg = format!("{}:{:?}", err.path().to_string(), err);
+            JsonResponse::<forms::project::ProjectForm>::build().bad_request(msg)
+        })
+        .and_then(|mut form: forms::project::ProjectForm| {
+            if !form.validate().is_ok() {
+                let errors = form.validate().unwrap_err().to_string();
+                let err_msg = format!("Invalid data received {:?}", &errors);
+                tracing::debug!(err_msg);
+
+                return Err(JsonResponse::<models::Project>::build().form_error(errors));
+            }
+
+            Ok(form)
+        })
+}
diff --git a/src/forms/project/mod.rs b/src/forms/project/mod.rs
index 29799fb1..b0463a99 100644
--- a/src/forms/project/mod.rs
+++ b/src/forms/project/mod.rs
@@ -1,6 +1,6 @@
 mod app;
 mod custom;
-mod form;
+pub(crate) mod form;
 mod port;
 mod payload;
 mod volumes;
diff --git a/src/forms/rating.rs b/src/forms/rating.rs
index 04b9b6b7..e3f82810 100644
--- a/src/forms/rating.rs
+++ b/src/forms/rating.rs
@@ -17,7 +17,7 @@ impl Into<models::Rating> for Rating {
     fn into(self) -> models::Rating {
         let mut rating = models::Rating::default();
         rating.obj_id = self.obj_id;
-        rating.category = self.category.into(); //todo change the type of category field to the RateCategory
+        rating.category = self.category.into();
         rating.hidden = Some(false);
         rating.rate = Some(self.rate);
         rating.comment = self.comment;
diff --git a/src/routes/project/add.rs b/src/routes/project/add.rs
index 729285c2..e1b84bc9 100644
--- a/src/routes/project/add.rs
+++ b/src/routes/project/add.rs
@@ -2,18 +2,16 @@ use crate::db;
 use crate::forms;
 use crate::helpers::JsonResponse;
 use crate::models;
-use actix_web::Error;
 use actix_web::{
     post, web,
     web::{Bytes, Data},
     Responder, Result,
 };
 use serde_json::Value;
-use serde_valid::Validate;
 use sqlx::PgPool;
-use std::str;
 use std::sync::Arc;
 use std::str::FromStr;
+use std::str;
 
 #[tracing::instrument(name = "Add project.")]
 #[post("")]
@@ -23,11 +21,9 @@ pub async fn item(
     pg_pool: Data<PgPool>,
 ) -> Result<impl Responder> {
     // @todo ACL
-    let form = body_into_form(body.clone()).await?;
+    let form = forms::project::form::body_into_form(body.clone()).await?;
     let project_name = form.custom.custom_stack_code.clone();
 
-    //let request_json = Some(serde_json::Value::from_str(r#"{"somefield": "somevalue"}"#).unwrap());
-    // let request_json = form.request_json.clone();
     let body_bytes = actix_web::body::to_bytes(body).await.unwrap();
     let body_str = str::from_utf8(&body_bytes)
         .map_err(|err| JsonResponse::<forms::project::ProjectForm>::build().internal_server_error(err.to_string()))?;
@@ -53,27 +49,3 @@ pub async fn item(
         })
 }
 
-
-async fn body_into_form(body: Bytes) -> Result<forms::project::ProjectForm, Error> {
-    let body_bytes = actix_web::body::to_bytes(body).await.unwrap();
-    let body_str = str::from_utf8(&body_bytes)
-        .map_err(|err| JsonResponse::<forms::project::ProjectForm>::build().internal_server_error(err.to_string()))?;
-    let deserializer = &mut serde_json::Deserializer::from_str(body_str);
-    serde_path_to_error::deserialize(deserializer)
-        .map_err(|err| {
-            let msg = format!("{}:{:?}", err.path().to_string(), err);
-            JsonResponse::<forms::project::ProjectForm>::build().bad_request(msg)
-        })
-        .and_then(|mut form: forms::project::ProjectForm| {
-            if !form.validate().is_ok() {
-                let errors = form.validate().unwrap_err().to_string();
-                let err_msg = format!("Invalid data received {:?}", &errors);
-                tracing::debug!(err_msg);
-
-                return Err(JsonResponse::<models::Project>::build().form_error(errors));
-            }
-
-            Ok(form)
-        })
-}
-
diff --git a/src/routes/project/update.rs b/src/routes/project/update.rs
index f59d45a6..ecd72b10 100644
--- a/src/routes/project/update.rs
+++ b/src/routes/project/update.rs
@@ -1,20 +1,22 @@
+use std::str::FromStr;
 use crate::forms;
 use crate::helpers::JsonResponse;
 use crate::models;
 use crate::db;
-use actix_web::{web, web::Data, Responder, Result, post};
+use actix_web::{web, web::Data, Responder, Result, put};
 use serde_json::Value;
 use serde_valid::Validate;
 use sqlx::PgPool;
 use std::sync::Arc;
+use actix_web::web::Bytes;
 use tracing::Instrument;
-use uuid::Uuid;
+use std::str;
 
 #[tracing::instrument(name = "Update project.")]
-#[post("/{id}")]
+#[put("/{id}")]
 pub async fn item(
     path: web::Path<(i32,)>,
-    form: web::Json<forms::project::ProjectForm>,
+    body: Bytes,
     user: web::ReqData<Arc<models::User>>,
     pg_pool: Data<PgPool>,
 ) -> Result<impl Responder> {
@@ -30,25 +32,35 @@ pub async fn item(
             None => Err(JsonResponse::<models::Project>::build().not_found("Project not found")),
         })?;
 
+    let body_bytes = actix_web::body::to_bytes(body.clone()).await.unwrap();
+    let body_str = str::from_utf8(&body_bytes)
+        .map_err(|err| JsonResponse::<forms::project::ProjectForm>::build().internal_server_error(err.to_string()))?;
+    let request_json = Value::from_str(body_str)?;
+    tracing::debug!("Request json: {:?}", request_json);
+
+    // @todo ACL
+    let form = forms::project::form::body_into_form(body.clone()).await?;
     tracing::debug!("form data: {:?}", form);
+
     if let Err(errors) = form.validate() {
         return Err(JsonResponse::<models::Project>::build().form_error(errors.to_string()));
     }
 
     let project_name = form.custom.custom_stack_code.clone();
-    let form_inner = form.into_inner();
 
-    if !form_inner.is_readable_docker_image().await.is_ok() {
+    if !form.is_readable_docker_image().await.is_ok() {
         return Err(JsonResponse::<models::Project>::build().bad_request("Can not access docker image"));
     }
 
-    let body: Value = serde_json::to_value::<forms::project::ProjectForm>(form_inner)
-        .map_err(|err| 
-            JsonResponse::<models::Project>::build().bad_request(format!("{err}"))
-        )?;
+    let body: Value = serde_json::to_value::<forms::project::ProjectForm>(form)
+        .or(serde_json::to_value::<forms::project::ProjectForm>(forms::project::ProjectForm::default()))
+        .unwrap();
+
 
     project.name = project_name;
     project.body = body;
+    project.request_json = request_json;
+
 
     db::project::update(pg_pool.get_ref(), project)
         .await
diff --git a/src/routes/server/add.rs b/src/routes/server/add.rs
index e625df58..020f55b9 100644
--- a/src/routes/server/add.rs
+++ b/src/routes/server/add.rs
@@ -68,8 +68,9 @@ pub async fn add(
             .ok("success"))
         .map_err(|err|
             match err {
+                // sqlx::error::DatabaseError::kind()
                 // sqlx::error::ErrorKind::ForeignKeyViolation => {
-                //     return JsonResponse::<models::Server>::build().bad_request("Failed to insert");
+                //     return JsonResponse::<models::Server>::build().bad_request("");
                 // }
                 _ => {
                     return JsonResponse::<models::Server>::build().internal_server_error("Failed to insert");

From b46620a251c85923968371b0b0f334f0940363ba Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Wed, 6 Mar 2024 13:24:05 +0200
Subject: [PATCH 208/284] icon attrs

---
 src/forms/project/icon_dark.rs  | 6 +++++-
 src/forms/project/icon_light.rs | 6 +++---
 2 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/src/forms/project/icon_dark.rs b/src/forms/project/icon_dark.rs
index 73c543e4..d488f6a6 100644
--- a/src/forms/project/icon_dark.rs
+++ b/src/forms/project/icon_dark.rs
@@ -1,4 +1,8 @@
 use serde::{Deserialize, Serialize};
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct IconDark {}
+pub struct IconDark {
+    width: Option<i32>,
+    height: Option<i32>,
+    image: Option<String>
+}
diff --git a/src/forms/project/icon_light.rs b/src/forms/project/icon_light.rs
index 8e9e4b81..90b2c6af 100644
--- a/src/forms/project/icon_light.rs
+++ b/src/forms/project/icon_light.rs
@@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct IconLight {
-    pub width: i64,
-    pub height: i64,
-    pub image: String,
+    pub width: Option<i64>,
+    pub height: Option<i64>,
+    pub image: Option<String>,
 }

From 5fad9d93c22f597b56762d11e898fed520e35aff Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Fri, 8 Mar 2024 08:57:39 +0200
Subject: [PATCH 209/284] 30-access-polices working on the general JsonConfig

---
 src/startup.rs | 30 ++++++++++++++++++++++++++++--
 1 file changed, 28 insertions(+), 2 deletions(-)

diff --git a/src/startup.rs b/src/startup.rs
index b8052662..1047bea2 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -4,8 +4,13 @@ use crate::routes;
 use actix_cors::Cors;
 use actix_web::dev::Server;
 use actix_web::{
-    web::{self},
-    App, HttpServer,
+    error,
+    web,
+    App,
+    HttpServer,
+    HttpResponse,
+    FromRequest,
+    rt,
 };
 use crate::middleware;
 use sqlx::{Pool, Postgres};
@@ -24,6 +29,26 @@ pub async fn run(
     let mq_manager = web::Data::new(mq_manager);
 
     let authorization = middleware::authorization::try_new(settings.database.connection_string()).await?;
+    let json_cfg = web::JsonConfig::default()
+        .error_handler(|err, req| {
+            let bytes = web::Bytes::from_request(req, &mut actix_web::dev::Payload::None);
+            let rt = rt::Runtime::new().unwrap();
+            let handle = rt.spawn(async move {
+                let bytes: web::Bytes = bytes.await.unwrap();
+            });
+
+            rt.block_on(handle).unwrap(); //todo 1. get the result of bytes. 
+                                          //todo 2. transform line, column into index
+                                          //todo 3. transform index in the start of json till the
+                                          //error occured
+
+            match err {
+                error::JsonPayloadError::Deserialize(ref err) => println!("deserialize {err:?} {err}"),
+                _ => println!("sth else {err:?}")
+            }
+
+            error::InternalError::from_response(err, HttpResponse::Conflict().into()).into()
+        });
 
     let server = HttpServer::new(move || {
         App::new()
@@ -74,6 +99,7 @@ pub async fn run(
                     .service(routes::stack::get::admin_item)
                     .service(routes::stack::get::admin_list)
             )
+            .app_data(json_cfg.clone())
             .app_data(pg_pool.clone())
             .app_data(mq_manager.clone())
             .app_data(settings.clone())

From 810bb6a0b4492122624e6969632393c9d995d4aa Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sun, 10 Mar 2024 02:10:32 +0200
Subject: [PATCH 210/284] 30-access-polices json errors

---
 src/routes/test/casbin.rs       |  8 -----
 src/routes/test/json_explain.rs | 37 +++++++++++++++++++++++
 src/routes/test/mod.rs          |  2 +-
 src/startup.rs                  | 53 +++++++++++++++++----------------
 4 files changed, 65 insertions(+), 35 deletions(-)
 delete mode 100644 src/routes/test/casbin.rs
 create mode 100644 src/routes/test/json_explain.rs

diff --git a/src/routes/test/casbin.rs b/src/routes/test/casbin.rs
deleted file mode 100644
index 77e1ec59..00000000
--- a/src/routes/test/casbin.rs
+++ /dev/null
@@ -1,8 +0,0 @@
-use actix_web::{get, Responder, Result};
-use crate::helpers::JsonResponse;
-
-#[tracing::instrument(name = "Test casbin.")]
-#[get("")]
-pub async fn handler() -> Result<impl Responder> {
-    Ok(JsonResponse::<i32>::build().ok("success"))
-}
diff --git a/src/routes/test/json_explain.rs b/src/routes/test/json_explain.rs
new file mode 100644
index 00000000..734a59a8
--- /dev/null
+++ b/src/routes/test/json_explain.rs
@@ -0,0 +1,37 @@
+use actix_web::{post, web, HttpResponse, Result, http::header::ContentType};
+
+#[tracing::instrument(name = "Json explain")]
+#[post("/json/explain/{line}/{column}")]
+pub async fn handler(path: web::Path<(usize, usize,)>, body: web::Bytes) -> HttpResponse {
+    let line = path.0;
+    let column = path.1;
+
+    let index = line_column_to_index(body.as_ref(), line, column);
+    let body = String::from_utf8(body.as_ref()[..index].to_vec()).unwrap(); //todo unwrap
+
+    HttpResponse::Ok()
+        .content_type(ContentType::plaintext())
+        .body(body)
+}
+
+fn line_column_to_index(u8slice: &[u8], line: usize, column: usize) -> usize {
+    let mut l = 1;
+    let mut c = 0;
+    let mut i = 0;
+    for ch in u8slice {
+        i += 1;
+        match ch {
+            b'\n' => {
+                l += 1;
+                c = 0;
+            }
+            _ => {
+                c += 1;
+            }
+        }
+        if line == l && c == column {
+            break;
+        }
+    }
+    return i;
+}
diff --git a/src/routes/test/mod.rs b/src/routes/test/mod.rs
index 2b55b427..9c490669 100644
--- a/src/routes/test/mod.rs
+++ b/src/routes/test/mod.rs
@@ -1,2 +1,2 @@
 pub mod deploy;
-pub mod casbin;
+pub mod json_explain;
diff --git a/src/startup.rs b/src/startup.rs
index 1047bea2..37892a3e 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -29,27 +29,6 @@ pub async fn run(
     let mq_manager = web::Data::new(mq_manager);
 
     let authorization = middleware::authorization::try_new(settings.database.connection_string()).await?;
-    let json_cfg = web::JsonConfig::default()
-        .error_handler(|err, req| {
-            let bytes = web::Bytes::from_request(req, &mut actix_web::dev::Payload::None);
-            let rt = rt::Runtime::new().unwrap();
-            let handle = rt.spawn(async move {
-                let bytes: web::Bytes = bytes.await.unwrap();
-            });
-
-            rt.block_on(handle).unwrap(); //todo 1. get the result of bytes. 
-                                          //todo 2. transform line, column into index
-                                          //todo 3. transform index in the start of json till the
-                                          //error occured
-
-            match err {
-                error::JsonPayloadError::Deserialize(ref err) => println!("deserialize {err:?} {err}"),
-                _ => println!("sth else {err:?}")
-            }
-
-            error::InternalError::from_response(err, HttpResponse::Conflict().into()).into()
-        });
-
     let server = HttpServer::new(move || {
         App::new()
             .wrap(TracingLogger::default())
@@ -73,10 +52,9 @@ pub async fn run(
                     .service(routes::client::admin_disable_handler),
             )
             .service(
-                web::scope("/test").service(routes::test::deploy::handler),
-            )
-            .service(
-                web::scope("/pen/1").service(routes::test::casbin::handler),
+                web::scope("/test")
+                    .service(routes::test::deploy::handler)
+                    .service(routes::test::json_explain::handler),
             )
             .service(
                 web::scope("/rating")
@@ -99,7 +77,6 @@ pub async fn run(
                     .service(routes::stack::get::admin_item)
                     .service(routes::stack::get::admin_list)
             )
-            .app_data(json_cfg.clone())
             .app_data(pg_pool.clone())
             .app_data(mq_manager.clone())
             .app_data(settings.clone())
@@ -109,3 +86,27 @@ pub async fn run(
 
     Ok(server)
 }
+
+fn line_column_to_index(u8slice: &[u8], line: usize, column: usize) -> usize {
+    let mut l = 1;
+    let mut c = 0;
+    let mut i = 0;
+    for ch in u8slice {
+        i += 1;
+        match ch {
+            b'\n' => {
+                l += 1;
+                c = 0;
+            }
+            _ => {
+                c += 1;
+            }
+        }
+
+        if line == l && c == column {
+            break;
+        }
+    }
+
+    return i;
+}

From a83071a7292816a9843cbf772a44cb13ba937017 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Mon, 11 Mar 2024 16:56:22 +0200
Subject: [PATCH 211/284] 30-access-policies json message for errors

---
 src/startup.rs | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/src/startup.rs b/src/startup.rs
index 37892a3e..2240565b 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -2,8 +2,9 @@ use crate::configuration::Settings;
 use crate::helpers;
 use crate::routes;
 use actix_cors::Cors;
-use actix_web::dev::Server;
 use actix_web::{
+    dev::Server,
+    http,
     error,
     web,
     App,
@@ -29,6 +30,14 @@ pub async fn run(
     let mq_manager = web::Data::new(mq_manager);
 
     let authorization = middleware::authorization::try_new(settings.database.connection_string()).await?;
+    let json_config = web::JsonConfig::default()
+        .error_handler(|err, req| {
+            let msg: String = match err {
+                 error::JsonPayloadError::Deserialize(err) => format!("{{\"kind\":\"deserialize\",\"line\":{}, \"column\":{}, \"msg\":\"{}\"}}", err.line(), err.column(), err),
+                 _ => format!("{{\"kind\":\"other\",\"msg\":\"{}\"}}", err)
+            };
+            error::InternalError::new(msg, http::StatusCode::BAD_REQUEST).into()
+        });
     let server = HttpServer::new(move || {
         App::new()
             .wrap(TracingLogger::default())
@@ -77,6 +86,7 @@ pub async fn run(
                     .service(routes::stack::get::admin_item)
                     .service(routes::stack::get::admin_list)
             )
+            .app_data(json_config.clone())
             .app_data(pg_pool.clone())
             .app_data(mq_manager.clone())
             .app_data(settings.clone())

From b402c83e9bb7c7faaea976ed9aaebf19774662af Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Mon, 11 Mar 2024 17:22:02 +0200
Subject: [PATCH 212/284] 30-access-policies json debug command. sketch

---
 src/console/commands/debug/json.rs | 23 +++++++++++++++++++++++
 src/console/commands/debug/mod.rs  |  3 +++
 src/console/commands/mod.rs        |  1 +
 src/console/main.rs                | 22 ++++++++++++++++++++--
 4 files changed, 47 insertions(+), 2 deletions(-)
 create mode 100644 src/console/commands/debug/json.rs
 create mode 100644 src/console/commands/debug/mod.rs

diff --git a/src/console/commands/debug/json.rs b/src/console/commands/debug/json.rs
new file mode 100644
index 00000000..63d90f53
--- /dev/null
+++ b/src/console/commands/debug/json.rs
@@ -0,0 +1,23 @@
+use crate::configuration::get_configuration;
+use actix_web::rt;
+use actix_web::web;
+use sqlx::PgPool;
+
+pub struct JsonCommand {
+    line: usize,
+    column: usize,
+    payload: String
+}
+
+impl JsonCommand {
+    pub fn new(line: usize, column: usize, payload: String) -> Self {
+        Self { line, column, payload }
+    }
+}
+
+impl crate::console::commands::CallableTrait for JsonCommand {
+    fn call(&self) -> Result<(), Box<dyn std::error::Error>> {
+        println!("line={} column={} payload={}", self.line, self.column, self.payload);
+        Ok(())
+    }
+}
diff --git a/src/console/commands/debug/mod.rs b/src/console/commands/debug/mod.rs
new file mode 100644
index 00000000..5f50076c
--- /dev/null
+++ b/src/console/commands/debug/mod.rs
@@ -0,0 +1,3 @@
+mod json;
+
+pub use json::*;
diff --git a/src/console/commands/mod.rs b/src/console/commands/mod.rs
index 2cf75be7..0b8d17a5 100644
--- a/src/console/commands/mod.rs
+++ b/src/console/commands/mod.rs
@@ -1,4 +1,5 @@
 pub mod appclient;
+pub mod debug;
 mod callable;
 
 pub use callable::*;
diff --git a/src/console/main.rs b/src/console/main.rs
index 09211286..5d5b168d 100644
--- a/src/console/main.rs
+++ b/src/console/main.rs
@@ -12,6 +12,10 @@ enum Commands {
         #[command(subcommand)]
         command: AppClientCommands,
     },
+    Debug {
+        #[command(subcommand)]
+        command: DebugCommands,
+    },
 }
 
 #[derive(Debug, Subcommand)]
@@ -22,8 +26,17 @@ enum AppClientCommands {
     },
 }
 
-//todo add documentation about how to add a new command
-//todo the helper from console should have a nicer display
+#[derive(Debug, Subcommand)]
+enum DebugCommands {
+    Json {
+        #[arg(long)]
+        line: usize,
+        #[arg(long)]
+        column: usize,
+        #[arg(long)]
+        payload: String,
+    },
+}
 
 fn main() -> Result<(), Box<dyn std::error::Error>> {
     let cli = Cli::parse();
@@ -38,6 +51,11 @@ fn get_command(cli: Cli) -> Result<Box<dyn stacker::console::commands::CallableT
                 stacker::console::commands::appclient::NewCommand::new(user_id),
             )),
         },
+        Commands::Debug { command } => match command {
+            DebugCommands::Json { line, column, payload } => Ok(Box::new(
+                stacker::console::commands::debug::JsonCommand::new(line, column, payload),
+            )),
+        },
         _ => Err("command does not match".to_string()),
     }
 }

From 2cf6a7599c63b825f6e0b32c3fccb82431126981 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Tue, 12 Mar 2024 16:58:54 +0200
Subject: [PATCH 213/284] payload fix for deploy command

---
 ...0230905145525_creating_stack_tables.up.sql |  2 +-
 ...7113718_alter_cloud_alter_project.down.sql |  3 +
 ...307113718_alter_cloud_alter_project.up.sql |  3 +
 src/db/cloud.rs                               | 24 +++---
 src/db/project.rs                             | 17 ++--
 src/db/server.rs                              | 15 +++-
 src/forms/cloud.rs                            | 19 ++++-
 src/forms/mod.rs                              |  2 +
 src/forms/project/payload.rs                  | 12 +--
 src/forms/server.rs                           | 22 ++++-
 src/helpers/project/builder.rs                |  4 +-
 src/models/cloud.rs                           |  1 +
 src/models/project.rs                         |  2 -
 src/routes/cloud/update.rs                    |  2 -
 src/routes/project/deploy.rs                  | 81 ++++++++++---------
 src/routes/server/add.rs                      |  4 -
 src/routes/server/mod.rs                      |  4 +-
 src/routes/server/update.rs                   | 57 +++++++++++++
 src/startup.rs                                |  2 +-
 tests/mock_data/project-update.json           |  6 ++
 20 files changed, 198 insertions(+), 84 deletions(-)
 create mode 100644 migrations/20240307113718_alter_cloud_alter_project.down.sql
 create mode 100644 migrations/20240307113718_alter_cloud_alter_project.up.sql
 create mode 100644 src/routes/server/update.rs
 create mode 100644 tests/mock_data/project-update.json

diff --git a/migrations/20230905145525_creating_stack_tables.up.sql b/migrations/20230905145525_creating_stack_tables.up.sql
index 6ae6433e..c002beb0 100644
--- a/migrations/20230905145525_creating_stack_tables.up.sql
+++ b/migrations/20230905145525_creating_stack_tables.up.sql
@@ -2,7 +2,7 @@ CREATE TABLE project (
     id serial4 NOT NULL,
     stack_id uuid NOT NULL,
     user_id VARCHAR(50) NOT NULL,
-    name TEXT,
+    name TEXT NOT NULL,
     body JSON NOT NULL,
     created_at timestamptz NOT NULL,
     updated_at timestamptz NOT NULL,
diff --git a/migrations/20240307113718_alter_cloud_alter_project.down.sql b/migrations/20240307113718_alter_cloud_alter_project.down.sql
new file mode 100644
index 00000000..06f51ab5
--- /dev/null
+++ b/migrations/20240307113718_alter_cloud_alter_project.down.sql
@@ -0,0 +1,3 @@
+-- Add down migration script here
+ALTER table project ADD COLUMN cloud_id INT CONSTRAINT project_cloud_id REFERENCES cloud(id) ON UPDATE CASCADE ON DELETE CASCADE;
+ALTER table cloud DROP COLUMN project_id;
\ No newline at end of file
diff --git a/migrations/20240307113718_alter_cloud_alter_project.up.sql b/migrations/20240307113718_alter_cloud_alter_project.up.sql
new file mode 100644
index 00000000..554a24a8
--- /dev/null
+++ b/migrations/20240307113718_alter_cloud_alter_project.up.sql
@@ -0,0 +1,3 @@
+-- Add up migration script here
+ALTER table project DROP COLUMN cloud_id;
+ALTER table cloud ADD COLUMN project_id INT CONSTRAINT cloud_project_id REFERENCES project(id) ON UPDATE CASCADE ON DELETE CASCADE;
diff --git a/src/db/cloud.rs b/src/db/cloud.rs
index 51c3f243..07f52851 100644
--- a/src/db/cloud.rs
+++ b/src/db/cloud.rs
@@ -48,6 +48,7 @@ pub async fn insert(pool: &PgPool, mut cloud: models::Cloud) -> Result<models::C
         r#"
         INSERT INTO cloud (
         user_id,
+        project_id,
         provider,
         cloud_token,
         cloud_key,
@@ -55,10 +56,11 @@ pub async fn insert(pool: &PgPool, mut cloud: models::Cloud) -> Result<models::C
         save_token,
         created_at,
         updated_at)
-        VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
+        VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
         RETURNING id;
         "#,
         cloud.user_id,
+        cloud.project_id,
         cloud.provider,
         cloud.cloud_token,
         cloud.cloud_key,
@@ -88,24 +90,24 @@ pub async fn update(pool: &PgPool, mut cloud: models::Cloud) -> Result<models::C
         UPDATE cloud
         SET
             user_id=$2,
-            provider=$3,
-            cloud_token=$4,
-            cloud_key=$5,
-            cloud_secret=$6,
-            save_token=$7,
-            created_at=$8,
+            project_id=$3,
+            provider=$4,
+            cloud_token=$5,
+            cloud_key=$6,
+            cloud_secret=$7,
+            save_token=$8,
             updated_at=NOW() at time zone 'utc'
         WHERE id = $1
         RETURNING *
         "#,
         cloud.id,
         cloud.user_id,
+        cloud.project_id,
         cloud.provider,
         cloud.cloud_token,
         cloud.cloud_key,
         cloud.cloud_secret,
-        cloud.save_token,
-        cloud.created_at,
+        cloud.save_token
     )
         .fetch_one(pool)
         .instrument(query_span)
@@ -143,14 +145,14 @@ pub async fn delete(pool: &PgPool, id: i32) -> Result<bool, String> {
         })
     {
         Ok(_) => {
-            tx.commit().await.map_err(|err| {
+            let _ = tx.commit().await.map_err(|err| {
                 tracing::error!("Failed to commit transaction: {:?}", err);
                 false
             });
             Ok(true)
         }
         Err(err) => {
-            tx.rollback().await.map_err(|err| println!("{:?}", err));
+            let _ = tx.rollback().await.map_err(|err| println!("{:?}", err));
             Ok(false)
         }
     }
diff --git a/src/db/project.rs b/src/db/project.rs
index fdeb6639..d1f469f8 100644
--- a/src/db/project.rs
+++ b/src/db/project.rs
@@ -78,8 +78,8 @@ pub async fn insert(pool: &PgPool, mut project: models::Project) -> Result<model
     let query_span = tracing::info_span!("Saving new project into the database");
     sqlx::query!(
         r#"
-        INSERT INTO project (stack_id, user_id, name, body, created_at, updated_at, cloud_id, request_json)
-        VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
+        INSERT INTO project (stack_id, user_id, name, body, created_at, updated_at, request_json)
+        VALUES ($1, $2, $3, $4, $5, $6, $7)
         RETURNING id;
         "#,
         project.stack_id,
@@ -88,7 +88,6 @@ pub async fn insert(pool: &PgPool, mut project: models::Project) -> Result<model
         project.body,
         project.created_at,
         project.updated_at,
-        project.cloud_id,
         project.request_json,
     )
     .fetch_one(pool)
@@ -113,11 +112,9 @@ pub async fn update(pool: &PgPool, mut project: models::Project) -> Result<model
         SET 
             stack_id=$2,
             user_id=$3,
-            cloud_id=$4,
-            name=$5,
-            body=$6,
-            request_json=$7,
-            created_at=$8,
+            name=$4,
+            body=$5,
+            request_json=$6,
             updated_at=NOW() at time zone 'utc'
         WHERE id = $1
         RETURNING *
@@ -125,11 +122,9 @@ pub async fn update(pool: &PgPool, mut project: models::Project) -> Result<model
         project.id,
         project.stack_id,
         project.user_id,
-        project.cloud_id,
         project.name,
         project.body,
-        project.request_json,
-        project.created_at,
+        project.request_json
     )
     .fetch_one(pool)
     .instrument(query_span)
diff --git a/src/db/server.rs b/src/db/server.rs
index 10a0ea65..6a0aa649 100644
--- a/src/db/server.rs
+++ b/src/db/server.rs
@@ -101,6 +101,15 @@ pub async fn insert(pool: &PgPool, mut server: models::Server) -> Result<models:
             server
         })
         .map_err(|e| {
+
+            // match err {
+            // sqlx::error::ErrorKind::ForeignKeyViolation => {
+            //     return JsonResponse::<models::Server>::build().bad_request("");
+            // }
+            //     _ => {
+            //         return JsonResponse::<models::Server>::build().internal_server_error("Failed to insert");
+            //     }
+            // })
             tracing::error!("Failed to execute query: {:?}", e);
             "Failed to insert".to_string()
         })
@@ -121,7 +130,6 @@ pub async fn update(pool: &PgPool, mut server: models::Server) -> Result<models:
             server=$7,
             os=$8,
             disk_type=$9,
-            created_at=$10,
             updated_at=NOW() at time zone 'utc'
         WHERE id = $1
         RETURNING *
@@ -135,7 +143,6 @@ pub async fn update(pool: &PgPool, mut server: models::Server) -> Result<models:
         server.server,
         server.os,
         server.disk_type,
-        server.created_at,
     )
         .fetch_one(pool)
         .instrument(query_span)
@@ -173,14 +180,14 @@ pub async fn delete(pool: &PgPool, id: i32) -> Result<bool, String> {
         })
     {
         Ok(_) => {
-            tx.commit().await.map_err(|err| {
+            let _ = tx.commit().await.map_err(|err| {
                 tracing::error!("Failed to commit transaction: {:?}", err);
                 false
             });
             Ok(true)
         }
         Err(err) => {
-            tx.rollback().await.map_err(|err| println!("{:?}", err));
+            let _ = tx.rollback().await.map_err(|err| println!("{:?}", err));
             Ok(false)
         }
     }
diff --git a/src/forms/cloud.rs b/src/forms/cloud.rs
index cbde8b07..9af0b4a1 100644
--- a/src/forms/cloud.rs
+++ b/src/forms/cloud.rs
@@ -3,9 +3,10 @@ use serde::{Deserialize, Serialize};
 use serde_valid::Validate;
 use chrono::{DateTime, Utc};
 
-#[derive(Serialize, Deserialize, Debug, Validate)]
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Cloud {
     pub user_id: String,
+    pub project_id: Option<i32>,
     #[validate(min_length = 2)]
     #[validate(max_length = 50)]
     pub provider: String,
@@ -19,6 +20,7 @@ impl Into<models::Cloud> for Cloud {
     fn into(self) -> models::Cloud {
         let mut cloud = models::Cloud::default();
         cloud.user_id = self.user_id;
+        cloud.project_id = self.project_id;
         cloud.provider = self.provider;
         cloud.cloud_token = self.cloud_token;
         cloud.cloud_key = self.cloud_key;
@@ -30,3 +32,18 @@ impl Into<models::Cloud> for Cloud {
         cloud
     }
 }
+
+impl Into<Cloud> for models::Cloud {
+    fn into(self) -> Cloud {
+        let mut form = Cloud::default();
+        form.user_id = self.user_id;
+        form.project_id = self.project_id;
+        form.provider = self.provider;
+        form.cloud_token = self.cloud_token;
+        form.cloud_key = self.cloud_key;
+        form.cloud_secret = self.cloud_secret;
+        form.save_token = self.save_token;
+
+        form
+    }
+}
diff --git a/src/forms/mod.rs b/src/forms/mod.rs
index f1522632..f5fcc9cb 100644
--- a/src/forms/mod.rs
+++ b/src/forms/mod.rs
@@ -5,3 +5,5 @@ pub(crate) mod cloud;
 pub(crate) mod server;
 
 pub use rating::*;
+pub use cloud::*;
+pub use server::*;
diff --git a/src/forms/project/payload.rs b/src/forms/project/payload.rs
index 70e8f60b..aee8a4c4 100644
--- a/src/forms/project/payload.rs
+++ b/src/forms/project/payload.rs
@@ -15,7 +15,7 @@ pub struct Payload {
     pub common_domain: String,
     pub domain_list: Option<forms::project::DomainList>,
     #[serde(flatten)]
-    pub server: models::Server,
+    pub server: Option<forms::Server>,
     pub ssl: String,
     pub vars: Option<Vec<forms::project::Var>>,
     #[serde(rename = "integrated_features")]
@@ -26,7 +26,7 @@ pub struct Payload {
     pub form_app: Option<Vec<String>>,
     pub disk_type: Option<String>,
     #[serde(flatten)]
-    pub cloud: models::Cloud,
+    pub cloud: Option<forms::Cloud>,
     pub stack_code: String,
     pub custom: forms::project::Custom,
     pub docker_compose: Option<Vec<u8>>,
@@ -36,9 +36,11 @@ impl TryFrom<&models::Project> for Payload {
     type Error = String;
 
     fn try_from(project: &models::Project) -> Result<Self, Self::Error> {
-        let mut project_data = serde_json::from_value::<Payload>(project.body.clone()).map_err(|err| {
-            format!("{:?}", err)
-        })?;
+        // tracing::debug!("project body: {:?}", project.body.clone());
+        let mut project_data = serde_json::from_value::<Payload>(project.body.clone())
+            .map_err(|err| {
+                format!("{:?}", err)
+            })?;
 
         project_data.id = Some(project.id.clone());
         project_data.stack_code = project_data.custom.custom_stack_code.clone();
diff --git a/src/forms/server.rs b/src/forms/server.rs
index fac003f5..d66cde0a 100644
--- a/src/forms/server.rs
+++ b/src/forms/server.rs
@@ -2,8 +2,9 @@ use crate::models;
 use serde::{Deserialize, Serialize};
 use serde_valid::Validate;
 use chrono::{DateTime, Utc};
-use crate::db;
-#[derive(Serialize, Deserialize, Debug, Validate)]
+use crate::forms;
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Server {
     pub user_id: String,
     pub cloud_id: i32,
@@ -32,3 +33,20 @@ impl Into<models::Server> for Server {
         server
     }
 }
+
+impl Into<Server> for models::Server {
+
+    fn into(self) -> Server {
+        let mut form = Server::default();
+        form.user_id = self.user_id;
+        form.cloud_id = self.cloud_id;
+        form.project_id = self.project_id;
+        form.disk_type = self.disk_type;
+        form.region = self.region;
+        form.server = self.server;
+        form.zone = self.zone;
+        form.os = self.os;
+
+        form
+    }
+}
diff --git a/src/helpers/project/builder.rs b/src/helpers/project/builder.rs
index b5b0e42b..7112fe30 100644
--- a/src/helpers/project/builder.rs
+++ b/src/helpers/project/builder.rs
@@ -30,9 +30,12 @@ impl DcBuilder {
         };
 
         let apps = forms::project::ProjectForm::try_from(&self.project)?;
+        tracing::debug!("apps {:?}", &apps);
         let services = apps.custom.services()?;
+        tracing::debug!("services {:?}", &services);
         let named_volumes = apps.custom.named_volumes()?;
 
+        tracing::debug!("named volumes {:?}", &named_volumes);
         // let all_networks = &apps.custom.networks.networks.clone().unwrap_or(vec![]);
         let networks = apps.custom.networks.clone();
         compose_content.networks = dctypes::ComposeNetworks(networks.into());
@@ -41,7 +44,6 @@ impl DcBuilder {
             compose_content.volumes = dctypes::TopLevelVolumes(named_volumes);
         }
 
-        tracing::debug!("services {:?}", &services);
         compose_content.services = dctypes::Services(services);
 
         let fname = format!("./files/{}.yml", self.project.stack_id);
diff --git a/src/models/cloud.rs b/src/models/cloud.rs
index 308950dd..91b494c1 100644
--- a/src/models/cloud.rs
+++ b/src/models/cloud.rs
@@ -5,6 +5,7 @@ use serde_derive::{Deserialize, Serialize};
 pub struct Cloud {
     pub id: i32,
     pub user_id: String,
+    pub project_id: Option<i32>,
     pub provider: String,
     pub cloud_token: Option<String>,
     pub cloud_key: Option<String>,
diff --git a/src/models/project.rs b/src/models/project.rs
index 1f33df8b..77422675 100644
--- a/src/models/project.rs
+++ b/src/models/project.rs
@@ -6,7 +6,6 @@ use uuid::Uuid;
 #[derive(Debug, Serialize, Deserialize, Clone)]
 pub struct Project {
     pub id: i32,         // id - is a unique identifier for the app project
-    pub cloud_id: Option<i32>,   // cloud assigned to a project
     pub stack_id: Uuid,  // external project ID
     pub user_id: String, // external unique identifier for the user
     pub name: String,
@@ -22,7 +21,6 @@ impl Project {
         Self {
             id: 0,
             stack_id: Uuid::new_v4(),
-            cloud_id: None,
             user_id,
             name,
             body,
diff --git a/src/routes/cloud/update.rs b/src/routes/cloud/update.rs
index d4fd2387..2a69237a 100644
--- a/src/routes/cloud/update.rs
+++ b/src/routes/cloud/update.rs
@@ -36,8 +36,6 @@ pub async fn item(
     let mut cloud:models::Cloud = form.into_inner().into();
     cloud.id = cloud_row.id;
     cloud.user_id = user.id.clone();
-    // exclude
-    // cloud.created_at
 
     tracing::debug!("Updating cloud {:?}", cloud);
 
diff --git a/src/routes/project/deploy.rs b/src/routes/project/deploy.rs
index f3144f06..6bbb4cd7 100644
--- a/src/routes/project/deploy.rs
+++ b/src/routes/project/deploy.rs
@@ -12,15 +12,19 @@ use crate::helpers::compressor::compress;
 
 
 #[tracing::instrument(name = "Deploy for every user. Admin endpoint")]
-#[post("/{id}/deploy")]
-pub async fn add(
+#[post("/{id}/deploy/{cloud_id}")]
+pub async fn item(
     user: web::ReqData<Arc<models::User>>,
-    path: web::Path<(i32,)>,
+    path: web::Path<(i32, i32)>,
     pg_pool: Data<PgPool>,
     mq_manager: Data<MqManager>,
     sets: Data<Settings>,
 ) -> Result<impl Responder> {
     let id = path.0;
+    let cloud_id = path.1;
+    //let cloud_id = Some(1);
+    tracing::debug!("User {:?} is deploying project: {} to cloud: {} ", user, id, cloud_id);
+
     let project = db::project::fetch(pg_pool.get_ref(), id)
         .await
         .map_err(|err| JsonResponse::<models::Project>::build().internal_server_error(err))
@@ -35,43 +39,43 @@ pub async fn add(
         JsonResponse::<models::Project>::build().internal_server_error(err)
     })?;
 
-    let mut project_data = forms::project::Payload::try_from(&dc.project)
-        .map_err(|err| JsonResponse::<models::Project>::build().bad_request(err))?;
-    project_data.user_token = Some(user.id.clone());
-    project_data.user_email = Some(user.email.clone());
-    // let compressed = fc.unwrap_or("".to_string());
-    project_data.docker_compose = Some(compress(fc.as_str()));
-
-    if let Some(cloud_id) = dc.project.cloud_id {
-        project_data.cloud = match db::cloud::fetch(pg_pool.get_ref(), cloud_id).await {
-            Ok(cloud) => {
-                match cloud {
-                    Some(cloud) => cloud,
-                    None => {
-                        return Err(JsonResponse::<models::Project>::build().not_found("No cloud configured"));
-                    }
+    let cloud = match db::cloud::fetch(pg_pool.get_ref(), cloud_id).await {
+        Ok(cloud) => {
+            match cloud {
+                Some(cloud) => cloud,
+                None => {
+                    return Err(JsonResponse::<models::Project>::build().not_found("No cloud configured"));
                 }
             }
-            Err(e) => {
-                return Err(JsonResponse::<models::Project>::build().not_found("No cloud configured"));
-            }
-        };
+        }
+        Err(e) => {
+            return Err(JsonResponse::<models::Project>::build().not_found("No cloud configured"));
+        }
+    };
 
-        project_data.server = match db::server::fetch_by_project(pg_pool.get_ref(), dc.project.id.clone()).await {
-            Ok(server) => {
-                // for now we support only one type of servers
-                // if let Some(server) = server.into_iter().nth(0) {
-                //     server
-                // }
-                server.into_iter().nth(0).unwrap() // @todo refactoring is required
-            }
-            Err(err) => {
-                return Err(JsonResponse::<models::Project>::build().not_found("No servers configured"));
-            }
+    let server = match db::server::fetch_by_project(pg_pool.get_ref(), dc.project.id.clone()).await {
+        Ok(server) => {
+            // for now we support only one type of servers
+            // if let Some(server) = server.into_iter().nth(0) {
+            //     server
+            // }
+            server.into_iter().nth(0).unwrap() // @todo refactoring is required
         }
-    } else {
-        return Err(JsonResponse::<models::Project>::build().not_found("No cloud provider configured"));
-    }
+        Err(err) => {
+            return Err(JsonResponse::<models::Project>::build().not_found("No servers configured"));
+        }
+    };
+
+    // let mut payload = forms::project::Payload::default();
+    let mut payload = forms::project::Payload::try_from(&dc.project)
+        .map_err(|err| JsonResponse::<models::Project>::build().bad_request(err))?;
+    payload.server = Some(server.into());
+    payload.cloud  = Some(cloud.into());
+    payload.user_token = Some(user.id.clone());
+    payload.user_email = Some(user.email.clone());
+    // let compressed = fc.unwrap_or("".to_string());
+    payload.docker_compose = Some(compress(fc.as_str()));
+
 
     let project_id = dc.project.id.clone();
     let json_request = dc.project.body.clone();
@@ -89,12 +93,13 @@ pub async fn add(
         });
 
     tracing::debug!("Save deployment result: {:?}", result);
+    tracing::debug!("Send project data <<<<<<<<<<<>>>>>>>>>>>>>>>>{:?}", payload);
 
     mq_manager
-        .publish_and_confirm(
+        .publish(
             "install".to_string(),
             "install.start.tfa.all.all".to_string(),
-            &project_data,
+            &payload,
         )
         .await
         .map_err(|err| JsonResponse::<models::Project>::build().internal_server_error(err))
diff --git a/src/routes/server/add.rs b/src/routes/server/add.rs
index 020f55b9..96ea3908 100644
--- a/src/routes/server/add.rs
+++ b/src/routes/server/add.rs
@@ -68,10 +68,6 @@ pub async fn add(
             .ok("success"))
         .map_err(|err|
             match err {
-                // sqlx::error::DatabaseError::kind()
-                // sqlx::error::ErrorKind::ForeignKeyViolation => {
-                //     return JsonResponse::<models::Server>::build().bad_request("");
-                // }
                 _ => {
                     return JsonResponse::<models::Server>::build().internal_server_error("Failed to insert");
                 }
diff --git a/src/routes/server/mod.rs b/src/routes/server/mod.rs
index 551560fb..319c89fb 100644
--- a/src/routes/server/mod.rs
+++ b/src/routes/server/mod.rs
@@ -1,7 +1,9 @@
 pub mod add;
 pub(crate) mod get;
 pub(crate) mod delete;
+mod update;
 
-pub use add::*;
 pub use get::*;
+pub use add::*;
+pub use update::*;
 pub use delete::*;
diff --git a/src/routes/server/update.rs b/src/routes/server/update.rs
new file mode 100644
index 00000000..b7087781
--- /dev/null
+++ b/src/routes/server/update.rs
@@ -0,0 +1,57 @@
+use crate::forms;
+use crate::helpers::JsonResponse;
+use crate::models;
+use crate::db;
+use actix_web::{web, web::Data, Responder, Result, post, put};
+use serde_valid::Validate;
+use sqlx::PgPool;
+use std::sync::Arc;
+use tracing::Instrument;
+
+#[tracing::instrument(name = "Update server.")]
+#[put("/{id}")]
+pub async fn item(
+    path: web::Path<(i32,)>,
+    form: web::Json<forms::server::Server>,
+    user: web::ReqData<Arc<models::User>>,
+    pg_pool: Data<PgPool>,
+) -> Result<impl Responder> {
+
+    let id = path.0;
+    let mut server_row = db::server::fetch(pg_pool.get_ref(), id)
+        .await
+        .map_err(|err| JsonResponse::<models::Server>::build().internal_server_error(err))
+        .and_then(|server| match server {
+            Some(server) if server.user_id != user.id => {
+                Err(JsonResponse::<models::Project>::build().bad_request("Server not found"))
+            }
+            Some(server) => Ok(server),
+            None => Err(JsonResponse::<models::Server>::build().not_found("Server not found")),
+        })?;
+
+    if let Err(errors) = form.validate() {
+        return Err(JsonResponse::<models::Server>::build().form_error(errors.to_string()));
+    }
+
+    let mut server:models::Server = form.into_inner().into();
+    server.id = server_row.id;
+    server.cloud_id = server_row.cloud_id;
+    server.project_id = server_row.project_id;
+    server.user_id = user.id.clone();
+    // exclude
+    // server.created_at
+
+    tracing::debug!("Updating server {:?}", server);
+
+    db::server::update(pg_pool.get_ref(), server)
+        .await
+        .map(|server| {
+            JsonResponse::<models::Server>::build()
+                .set_item(server)
+                .ok("success")
+        })
+        .map_err(|err| {
+            tracing::error!("Failed to execute query: {:?}", err);
+            JsonResponse::<models::Server>::build().internal_server_error("Could not update server")
+        })
+}
diff --git a/src/startup.rs b/src/startup.rs
index 06ada2a0..612d3219 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -71,7 +71,7 @@ pub async fn run(
                         middleware::trydirect::bearer_guard,
                     ))
                     .wrap(Cors::permissive())
-                    .service(crate::routes::project::deploy::add)
+                    .service(crate::routes::project::deploy::item)
                     .service(crate::routes::project::compose::add)
                     .service(crate::routes::project::compose::admin)
                     .service(crate::routes::project::get::item)
diff --git a/tests/mock_data/project-update.json b/tests/mock_data/project-update.json
new file mode 100644
index 00000000..4c0e7c7c
--- /dev/null
+++ b/tests/mock_data/project-update.json
@@ -0,0 +1,6 @@
+{
+  "stack_id": "9239ea1d-8306-4493-aae1-fcc00de76241",
+  "user_id": "hy181TZa4DaabUZWklsrxw",
+  "name": "sample",
+  "body": "{}"
+}
\ No newline at end of file

From d708f82787398988755d13c49a3b13786ee6ffcc Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Wed, 13 Mar 2024 16:50:37 +0200
Subject: [PATCH 214/284] 30-access-policies read payload from file

---
 src/console/commands/debug/json.rs | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/console/commands/debug/json.rs b/src/console/commands/debug/json.rs
index 63d90f53..544b1b5f 100644
--- a/src/console/commands/debug/json.rs
+++ b/src/console/commands/debug/json.rs
@@ -17,7 +17,8 @@ impl JsonCommand {
 
 impl crate::console::commands::CallableTrait for JsonCommand {
     fn call(&self) -> Result<(), Box<dyn std::error::Error>> {
-        println!("line={} column={} payload={}", self.line, self.column, self.payload);
+        let payload = std::fs::read_to_string(&self.payload)?;
+        println!("line={} column={} payload={}", self.line, self.column, payload);
         Ok(())
     }
 }

From fd47bfc7a292e908046386038b43a5653bd9ce3e Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Fri, 15 Mar 2024 16:56:31 +0200
Subject: [PATCH 215/284] 30-access-policies - json debug command

---
 src/console/commands/debug/json.rs | 33 ++++++++++++++++++++++----
 src/routes/test/json_explain.rs    | 37 ------------------------------
 src/routes/test/mod.rs             |  1 -
 src/startup.rs                     |  1 -
 4 files changed, 28 insertions(+), 44 deletions(-)
 delete mode 100644 src/routes/test/json_explain.rs

diff --git a/src/console/commands/debug/json.rs b/src/console/commands/debug/json.rs
index 544b1b5f..047faf0f 100644
--- a/src/console/commands/debug/json.rs
+++ b/src/console/commands/debug/json.rs
@@ -1,7 +1,5 @@
 use crate::configuration::get_configuration;
-use actix_web::rt;
-use actix_web::web;
-use sqlx::PgPool;
+use actix_web::{rt, post, web, HttpResponse, Result, http::header::ContentType};
 
 pub struct JsonCommand {
     line: usize,
@@ -17,8 +15,33 @@ impl JsonCommand {
 
 impl crate::console::commands::CallableTrait for JsonCommand {
     fn call(&self) -> Result<(), Box<dyn std::error::Error>> {
-        let payload = std::fs::read_to_string(&self.payload)?;
-        println!("line={} column={} payload={}", self.line, self.column, payload);
+        let payload: String = std::fs::read_to_string(&self.payload)?;
+        let index = line_column_to_index(payload.as_ref(), self.line, self.column);
+        let prefix = String::from_utf8(<std::string::String as AsRef<[u8]>>::as_ref(&payload)[..index].to_vec()).unwrap(); 
+
+        println!("{}", prefix);
         Ok(())
     }
 }
+
+fn line_column_to_index(u8slice: &[u8], line: usize, column: usize) -> usize {
+    let mut l = 1;
+    let mut c = 0;
+    let mut i = 0;
+    for ch in u8slice {
+        i += 1;
+        match ch {
+            b'\n' => {
+                l += 1;
+                c = 0;
+            }
+            _ => {
+                c += 1;
+            }
+        }
+        if line == l && c == column {
+            break;
+        }
+    }
+    return i;
+}
diff --git a/src/routes/test/json_explain.rs b/src/routes/test/json_explain.rs
deleted file mode 100644
index 734a59a8..00000000
--- a/src/routes/test/json_explain.rs
+++ /dev/null
@@ -1,37 +0,0 @@
-use actix_web::{post, web, HttpResponse, Result, http::header::ContentType};
-
-#[tracing::instrument(name = "Json explain")]
-#[post("/json/explain/{line}/{column}")]
-pub async fn handler(path: web::Path<(usize, usize,)>, body: web::Bytes) -> HttpResponse {
-    let line = path.0;
-    let column = path.1;
-
-    let index = line_column_to_index(body.as_ref(), line, column);
-    let body = String::from_utf8(body.as_ref()[..index].to_vec()).unwrap(); //todo unwrap
-
-    HttpResponse::Ok()
-        .content_type(ContentType::plaintext())
-        .body(body)
-}
-
-fn line_column_to_index(u8slice: &[u8], line: usize, column: usize) -> usize {
-    let mut l = 1;
-    let mut c = 0;
-    let mut i = 0;
-    for ch in u8slice {
-        i += 1;
-        match ch {
-            b'\n' => {
-                l += 1;
-                c = 0;
-            }
-            _ => {
-                c += 1;
-            }
-        }
-        if line == l && c == column {
-            break;
-        }
-    }
-    return i;
-}
diff --git a/src/routes/test/mod.rs b/src/routes/test/mod.rs
index 9c490669..40149b14 100644
--- a/src/routes/test/mod.rs
+++ b/src/routes/test/mod.rs
@@ -1,2 +1 @@
 pub mod deploy;
-pub mod json_explain;
diff --git a/src/startup.rs b/src/startup.rs
index 2240565b..46d0b5b6 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -63,7 +63,6 @@ pub async fn run(
             .service(
                 web::scope("/test")
                     .service(routes::test::deploy::handler)
-                    .service(routes::test::json_explain::handler),
             )
             .service(
                 web::scope("/rating")

From d89fb335de32db02c9a8aa31294997de6346ba27 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Fri, 15 Mar 2024 17:10:23 +0200
Subject: [PATCH 216/284] deploy function re-factoring, rabbitmq listener
 non-functional yet

---
 ...43712_remove_cloud_id_from_server.down.sql |   3 +
 ...5143712_remove_cloud_id_from_server.up.sql |   2 +
 src/console/commands/mod.rs                   |   2 +
 src/console/commands/mq/listener.rs           |  88 +++++++++++
 src/console/commands/mq/mod.rs                |   2 +
 src/console/main.rs                           |  15 ++
 src/db/deployment.rs                          |  70 +++++----
 src/db/server.rs                              |  18 +--
 src/forms/cloud.rs                            |   8 +-
 src/forms/project/deploy.rs                   |  27 ++++
 src/forms/project/form.rs                     |  16 --
 src/forms/project/mod.rs                      |   2 +
 src/forms/project/payload.rs                  |  18 +--
 src/forms/server.rs                           |  16 +-
 src/helpers/mod.rs                            |   4 +-
 src/helpers/mq_manager.rs                     |  79 ++++++++--
 src/models/deployment.rs                      |   4 +-
 src/models/mod.rs                             |   2 +-
 src/models/server.rs                          |   1 -
 src/routes/cloud/update.rs                    |   2 +-
 src/routes/project/deploy.rs                  | 118 ++++++++++++++-
 src/routes/server/add.rs                      | 139 +++++++++---------
 src/routes/server/mod.rs                      |   2 +-
 src/routes/server/update.rs                   |   1 -
 src/startup.rs                                |   4 +-
 tests/mock_data/deploy.json                   |   0
 tests/mock_data/deploy2.json                  |   1 +
 27 files changed, 452 insertions(+), 192 deletions(-)
 create mode 100644 migrations/20240315143712_remove_cloud_id_from_server.down.sql
 create mode 100644 migrations/20240315143712_remove_cloud_id_from_server.up.sql
 create mode 100644 src/console/commands/mq/listener.rs
 create mode 100644 src/console/commands/mq/mod.rs
 create mode 100644 src/forms/project/deploy.rs
 create mode 100644 tests/mock_data/deploy.json
 create mode 100644 tests/mock_data/deploy2.json

diff --git a/migrations/20240315143712_remove_cloud_id_from_server.down.sql b/migrations/20240315143712_remove_cloud_id_from_server.down.sql
new file mode 100644
index 00000000..72dd11e2
--- /dev/null
+++ b/migrations/20240315143712_remove_cloud_id_from_server.down.sql
@@ -0,0 +1,3 @@
+-- Add down migration script here
+DROP INDEX idx_server_cloud_id;
+alter table server ADD column cloud_id integer NOT NULL;
diff --git a/migrations/20240315143712_remove_cloud_id_from_server.up.sql b/migrations/20240315143712_remove_cloud_id_from_server.up.sql
new file mode 100644
index 00000000..be9027c0
--- /dev/null
+++ b/migrations/20240315143712_remove_cloud_id_from_server.up.sql
@@ -0,0 +1,2 @@
+-- Add up migration script here
+alter table server drop column cloud_id;
diff --git a/src/console/commands/mod.rs b/src/console/commands/mod.rs
index 2cf75be7..585347d2 100644
--- a/src/console/commands/mod.rs
+++ b/src/console/commands/mod.rs
@@ -1,4 +1,6 @@
 pub mod appclient;
 mod callable;
+pub mod mq;
 
 pub use callable::*;
+pub use mq::*;
diff --git a/src/console/commands/mq/listener.rs b/src/console/commands/mq/listener.rs
new file mode 100644
index 00000000..75cae061
--- /dev/null
+++ b/src/console/commands/mq/listener.rs
@@ -0,0 +1,88 @@
+use crate::configuration::get_configuration;
+use actix_web::rt;
+use actix_web::web;
+use lapin::{Channel, Queue};
+use lapin::options::{BasicAckOptions, BasicConsumeOptions};
+use lapin::types::FieldTable;
+use sqlx::PgPool;
+use db::deployment;
+use crate::{db, helpers};
+use crate::helpers::mq_manager;
+use crate::helpers::mq_manager::MqManager;
+
+pub struct ListenCommand {
+}
+
+impl ListenCommand {
+    pub fn new() -> Self {
+        Self {}
+    }
+}
+
+impl crate::console::commands::CallableTrait for ListenCommand {
+
+    fn call(&self) -> Result<(), Box<dyn std::error::Error>> {
+        rt::System::new().block_on(async {
+            let settings = get_configuration().expect("Failed to read configuration.");
+            let db_pool = PgPool::connect(&settings.database.connection_string())
+                .await
+                .expect("Failed to connect to database.");
+
+            let db_pool = web::Data::new(db_pool);
+
+            let mq_manager = MqManager::try_new(settings.amqp.connection_string())?;
+            let consumer_channel= mq_manager
+                .consume(
+                    "install_progress",
+                    "install_progress_*******"
+                )
+                .await?;
+
+
+            let consumer = consumer_channel
+                .basic_consume(
+                    "install_progress",
+                    "console_listener",
+                    BasicConsumeOptions::default(),
+                    FieldTable::default(),
+                )
+                .await
+                .expect("Basic consume");
+
+                // .map_err(|err| format!("Error {:?}", err));
+
+            tracing::info!("will consume");
+            // if let Ok(consumer) = consumer {
+            //     while let Some(delivery) = consumer.next().await {
+            //         let delivery = delivery.expect("error in consumer");
+            //         delivery.ack(BasicAckOptions::default()).await.expect("ack");
+            //     }
+            // }
+
+            // while let Some(delivery) = consumer.next().await {
+            //     tracing::debug!(message=?delivery, "received message");
+            //     if let Ok(delivery) = delivery {
+            //         delivery
+            //             .ack(BasicAckOptions::default())
+            //             .await
+            //             .expect("basic_ack");
+            //     }
+            // }
+
+
+            // on_complete()
+            // let deployment = crate::models::deployment::Deployment {
+            //     id: 0,
+            //     project_id: 0,
+            //     deleted: false,
+            //     status: "".to_string(),
+            //     body: Default::default(),
+            //     created_at: Default::default(),
+            //     updated_at: Default::default(),
+            // };
+            // deployment::update(db_pool.get_ref(), deployment).await?;
+
+            Ok(())
+        })
+    }
+}
diff --git a/src/console/commands/mq/mod.rs b/src/console/commands/mq/mod.rs
new file mode 100644
index 00000000..0d4c7ef8
--- /dev/null
+++ b/src/console/commands/mq/mod.rs
@@ -0,0 +1,2 @@
+mod listener;
+pub use listener::*;
\ No newline at end of file
diff --git a/src/console/main.rs b/src/console/main.rs
index 09211286..752e10ae 100644
--- a/src/console/main.rs
+++ b/src/console/main.rs
@@ -12,6 +12,10 @@ enum Commands {
         #[command(subcommand)]
         command: AppClientCommands,
     },
+    MQ {
+        #[command(subcommand)]
+        command: AppMqCommands,
+    }
 }
 
 #[derive(Debug, Subcommand)]
@@ -22,6 +26,12 @@ enum AppClientCommands {
     },
 }
 
+#[derive(Debug, Subcommand)]
+enum AppMqCommands {
+    Listen {
+    },
+}
+
 //todo add documentation about how to add a new command
 //todo the helper from console should have a nicer display
 
@@ -38,6 +48,11 @@ fn get_command(cli: Cli) -> Result<Box<dyn stacker::console::commands::CallableT
                 stacker::console::commands::appclient::NewCommand::new(user_id),
             )),
         },
+        Commands::MQ { command} => match command {
+            AppMqCommands::Listen {} => Ok(Box::new(
+                stacker::console::commands::mq::ListenCommand::new(),
+            )),
+        },
         _ => Err("command does not match".to_string()),
     }
 }
diff --git a/src/db/deployment.rs b/src/db/deployment.rs
index 1bfb3b39..11e7a2e3 100644
--- a/src/db/deployment.rs
+++ b/src/db/deployment.rs
@@ -30,39 +30,37 @@ pub async fn insert(pool: &PgPool, mut deployment: models::Deployment) -> Result
         })
 }
 
-// pub async fn update(pool: &PgPool, mut deployment: models::Deployment) -> Result<models::Deployment, String> {
-//     let query_span = tracing::info_span!("Updating user deployment into the database");
-//     sqlx::query_as!(
-//         models::Deployment,
-//         r#"
-//         UPDATE deployment
-//         SET
-//             project_id=$2,
-//             deleted=$3,
-//             status=$4,
-//             body=$5,
-//             created_at=$6,
-//             updated_at=NOW() at time zone 'utc'
-//         WHERE id = $1
-//         RETURNING *
-//         "#,
-//         deployment.id,
-//         deployment.project_id,
-//         deployment.deleted,
-//         deployment.status,
-//         deployment.body,
-//         deployment.created_at,
-//     )
-//         .fetch_one(pool)
-//         .instrument(query_span)
-//         .await
-//         .map(|result|{
-//             tracing::info!("Deployment {} has been saved to database", deployment.id);
-//             deployment.updated_at = result.updated_at;
-//             deployment
-//         })
-//         .map_err(|err| {
-//             tracing::error!("Failed to execute query: {:?}", err);
-//             "".to_string()
-//         })
-// }
+pub async fn update(pool: &PgPool, mut deployment: models::Deployment) -> Result<models::Deployment, String> {
+    let query_span = tracing::info_span!("Updating user deployment into the database");
+    sqlx::query_as!(
+        models::Deployment,
+        r#"
+        UPDATE deployment
+        SET
+            project_id=$2,
+            deleted=$3,
+            status=$4,
+            body=$5,
+            updated_at=NOW() at time zone 'utc'
+        WHERE id = $1
+        RETURNING *
+        "#,
+        deployment.id,
+        deployment.project_id,
+        deployment.deleted,
+        deployment.status,
+        deployment.body,
+    )
+        .fetch_one(pool)
+        .instrument(query_span)
+        .await
+        .map(|result|{
+            tracing::info!("Deployment {} has been updated", deployment.id);
+            deployment.updated_at = result.updated_at;
+            deployment
+        })
+        .map_err(|err| {
+            tracing::error!("Failed to execute query: {:?}", err);
+            "".to_string()
+        })
+}
diff --git a/src/db/server.rs b/src/db/server.rs
index 6a0aa649..86f147b2 100644
--- a/src/db/server.rs
+++ b/src/db/server.rs
@@ -70,7 +70,6 @@ pub async fn insert(pool: &PgPool, mut server: models::Server) -> Result<models:
         r#"
         INSERT INTO server (
         user_id,
-        cloud_id,
         project_id,
         region,
         zone,
@@ -79,11 +78,10 @@ pub async fn insert(pool: &PgPool, mut server: models::Server) -> Result<models:
         disk_type,
         created_at,
         updated_at)
-        VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
+        VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
         RETURNING id;
         "#,
         server.user_id,
-        server.cloud_id,
         server.project_id,
         server.region,
         server.zone,
@@ -123,20 +121,18 @@ pub async fn update(pool: &PgPool, mut server: models::Server) -> Result<models:
         UPDATE server
         SET
             user_id=$2,
-            cloud_id=$3,
-            project_id=$4,
-            region=$5,
-            zone=$6,
-            server=$7,
-            os=$8,
-            disk_type=$9,
+            project_id=$3,
+            region=$4,
+            zone=$5,
+            server=$6,
+            os=$7,
+            disk_type=$8,
             updated_at=NOW() at time zone 'utc'
         WHERE id = $1
         RETURNING *
         "#,
         server.id,
         server.user_id,
-        server.cloud_id,
         server.project_id,
         server.region,
         server.zone,
diff --git a/src/forms/cloud.rs b/src/forms/cloud.rs
index 9af0b4a1..138b2989 100644
--- a/src/forms/cloud.rs
+++ b/src/forms/cloud.rs
@@ -1,11 +1,10 @@
 use crate::models;
 use serde::{Deserialize, Serialize};
 use serde_valid::Validate;
-use chrono::{DateTime, Utc};
+use chrono::Utc;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Cloud {
-    pub user_id: String,
     pub project_id: Option<i32>,
     #[validate(min_length = 2)]
     #[validate(max_length = 50)]
@@ -19,15 +18,11 @@ pub struct Cloud {
 impl Into<models::Cloud> for Cloud {
     fn into(self) -> models::Cloud {
         let mut cloud = models::Cloud::default();
-        cloud.user_id = self.user_id;
-        cloud.project_id = self.project_id;
         cloud.provider = self.provider;
         cloud.cloud_token = self.cloud_token;
         cloud.cloud_key = self.cloud_key;
         cloud.cloud_secret = self.cloud_secret;
         cloud.save_token = self.save_token;
-        cloud.created_at = Utc::now();
-        cloud.updated_at = Utc::now();
 
         cloud
     }
@@ -36,7 +31,6 @@ impl Into<models::Cloud> for Cloud {
 impl Into<Cloud> for models::Cloud {
     fn into(self) -> Cloud {
         let mut form = Cloud::default();
-        form.user_id = self.user_id;
         form.project_id = self.project_id;
         form.provider = self.provider;
         form.cloud_token = self.cloud_token;
diff --git a/src/forms/project/deploy.rs b/src/forms/project/deploy.rs
new file mode 100644
index 00000000..ae4c5e8e
--- /dev/null
+++ b/src/forms/project/deploy.rs
@@ -0,0 +1,27 @@
+use serde_derive::{Deserialize, Serialize};
+use serde_json::Value;
+use serde_valid::Validate;
+use crate::forms;
+use crate::forms::{Cloud, Server};
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct Deploy {
+    #[validate]
+    pub(crate) stack: Stack,
+    #[validate]
+    pub(crate) server: Server,
+    #[validate]
+    pub(crate) cloud: Cloud
+}
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct Stack {
+    #[validate(min_length = 2)]
+    #[validate(max_length = 255)]
+    pub stack_code: Option<String>,
+    pub vars: Option<Vec<forms::project::Var>>,
+    pub integrated_features: Option<Vec<Value>>,
+    pub extended_features: Option<Vec<Value>>,
+    pub subscriptions: Option<Vec<String>>,
+    pub form_app: Option<Vec<String>>,
+}
\ No newline at end of file
diff --git a/src/forms/project/form.rs b/src/forms/project/form.rs
index 73f0eb23..d2498cf4 100644
--- a/src/forms/project/form.rs
+++ b/src/forms/project/form.rs
@@ -11,22 +11,6 @@ use std::str;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct ProjectForm {
-    // #[validate(min_length=2)]
-    // #[validate(max_length=255)]
-    #[serde(rename = "commonDomain")]
-    pub common_domain: Option<String>,
-    pub domain_list: Option<forms::project::DomainList>,
-    #[validate(min_length = 2)]
-    #[validate(max_length = 255)]
-    pub stack_code: Option<String>,
-    #[validate(min_length = 3)]
-    #[validate(max_length = 50)]
-    pub ssl: String,
-    pub vars: Option<Vec<forms::project::Var>>,
-    pub integrated_features: Option<Vec<Value>>,
-    pub extended_features: Option<Vec<Value>>,
-    pub subscriptions: Option<Vec<String>>,
-    pub form_app: Option<Vec<String>>,
     pub custom: forms::project::Custom
 }
 
diff --git a/src/forms/project/mod.rs b/src/forms/project/mod.rs
index b0463a99..d83fecca 100644
--- a/src/forms/project/mod.rs
+++ b/src/forms/project/mod.rs
@@ -24,6 +24,7 @@ mod icon_dark;
 mod version;
 
 mod network_driver;
+mod deploy;
 
 pub use app::*;
 pub use custom::*;
@@ -50,3 +51,4 @@ pub use icon::*;
 pub use icon_light::*;
 pub use icon_dark::*;
 pub use version::*;
+pub use deploy::*;
\ No newline at end of file
diff --git a/src/forms/project/payload.rs b/src/forms/project/payload.rs
index aee8a4c4..7108a1f5 100644
--- a/src/forms/project/payload.rs
+++ b/src/forms/project/payload.rs
@@ -11,23 +11,12 @@ pub struct Payload {
     pub(crate) id: Option<i32>,
     pub(crate) user_token: Option<String>,
     pub(crate) user_email: Option<String>,
-    #[serde(rename = "commonDomain")]
-    pub common_domain: String,
-    pub domain_list: Option<forms::project::DomainList>,
+    #[serde(flatten)]
+    pub cloud: Option<forms::Cloud>,
     #[serde(flatten)]
     pub server: Option<forms::Server>,
-    pub ssl: String,
-    pub vars: Option<Vec<forms::project::Var>>,
-    #[serde(rename = "integrated_features")]
-    pub integrated_features: Option<Vec<Value>>,
-    #[serde(rename = "extended_features")]
-    pub extended_features: Option<Vec<Value>>,
-    pub subscriptions: Option<Vec<String>>,
-    pub form_app: Option<Vec<String>>,
-    pub disk_type: Option<String>,
     #[serde(flatten)]
-    pub cloud: Option<forms::Cloud>,
-    pub stack_code: String,
+    pub stack: forms::project::Stack,
     pub custom: forms::project::Custom,
     pub docker_compose: Option<Vec<u8>>,
 }
@@ -43,7 +32,6 @@ impl TryFrom<&models::Project> for Payload {
             })?;
 
         project_data.id = Some(project.id.clone());
-        project_data.stack_code = project_data.custom.custom_stack_code.clone();
 
         Ok(project_data)
     }
diff --git a/src/forms/server.rs b/src/forms/server.rs
index d66cde0a..1e55a895 100644
--- a/src/forms/server.rs
+++ b/src/forms/server.rs
@@ -1,14 +1,12 @@
 use crate::models;
 use serde::{Deserialize, Serialize};
 use serde_valid::Validate;
-use chrono::{DateTime, Utc};
-use crate::forms;
+use chrono::{Utc};
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Server {
-    pub user_id: String,
-    pub cloud_id: i32,
-    pub project_id: i32,
+    // pub cloud_id: i32,
+    // pub project_id: i32,
     pub region: String,
     pub zone: Option<String>,
     pub server: String,
@@ -19,9 +17,6 @@ pub struct Server {
 impl Into<models::Server> for Server {
     fn into(self) -> models::Server {
         let mut server = models::Server::default();
-        server.user_id = self.user_id;
-        server.cloud_id = self.cloud_id;
-        server.project_id = self.project_id;
         server.disk_type = self.disk_type;
         server.region = self.region;
         server.server = self.server;
@@ -38,9 +33,8 @@ impl Into<Server> for models::Server {
 
     fn into(self) -> Server {
         let mut form = Server::default();
-        form.user_id = self.user_id;
-        form.cloud_id = self.cloud_id;
-        form.project_id = self.project_id;
+        // form.cloud_id = self.cloud_id;
+        // form.project_id = self.project_id;
         form.disk_type = self.disk_type;
         form.region = self.region;
         form.server = self.server;
diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs
index 74f975b5..0640df84 100644
--- a/src/helpers/mod.rs
+++ b/src/helpers/mod.rs
@@ -1,10 +1,10 @@
 pub mod client;
 pub(crate) mod json;
-mod mq_manager;
+pub mod mq_manager;
 pub mod project;
 
 pub use json::*;
-pub use mq_manager::MqManager;
+pub use mq_manager::*;
 pub mod dockerhub;
 pub(crate) mod compressor;
 
diff --git a/src/helpers/mq_manager.rs b/src/helpers/mq_manager.rs
index 5b058ea4..a7eb5e57 100644
--- a/src/helpers/mq_manager.rs
+++ b/src/helpers/mq_manager.rs
@@ -1,9 +1,8 @@
+use actix_web::web;
 use deadpool_lapin::{Config, CreatePoolError, Object, Pool, Runtime};
-use lapin::{
-    options::*,
-    publisher_confirm::{Confirmation, PublisherConfirm},
-    BasicProperties, Channel,
-};
+use lapin::{options::*, publisher_confirm::{Confirmation, PublisherConfirm}, BasicProperties, Channel, ExchangeKind, Queue};
+use lapin::types::AMQPType::ShortString;
+use lapin::types::{AMQPValue, FieldTable};
 use serde::ser::Serialize;
 
 #[derive(Debug)]
@@ -33,8 +32,9 @@ impl MqManager {
 
     async fn get_connection(&self) -> Result<Object, String> {
         self.pool.get().await.map_err(|err| {
-            tracing::error!("getting connection from pool {:?}", err);
-            format!("getting connection from pool {:?}", err)
+            let msg = format!("getting connection from pool {:?}", err);
+            tracing::error!(msg);
+            msg
         })
     }
 
@@ -44,8 +44,9 @@ impl MqManager {
             .create_channel()
             .await
             .map_err(|err| {
-                tracing::error!("creating RabbitMQ channel {:?}", err);
-                format!("creating RabbitMQ channel {:?}", err)
+                let msg = format!("creating RabbitMQ channel {:?}", err);
+                tracing::error!(msg);
+                msg
             })
     }
 
@@ -85,15 +86,67 @@ impl MqManager {
             .await?
             .await
             .map_err(|err| {
-                tracing::error!("confirming the publication {:?}", err);
-                format!("confirming the publication {:?}", err)
+                let msg = format!("confirming the publication {:?}", err);
+                tracing::error!(msg);
+                msg
+
             })
             .and_then(|confirm| match confirm {
                 Confirmation::NotRequested => {
-                    tracing::error!("confirmation is NotRequested");
-                    Err(format!("confirmation is NotRequested"))
+                    let msg = format!("confirmation is NotRequested");
+                    tracing::error!(msg);
+                    Err(msg)
                 }
                 _ => Ok(()),
             })
     }
+
+    pub async fn consume(
+        &self,
+        exchange_name: &str,
+        routing_key: &str,
+    ) -> Result<Channel, String> {
+
+        let mut args = FieldTable::default();
+        args.insert("x-expires".into(), AMQPValue::LongUInt(180000));
+        let channel = self.create_channel().await?;
+
+        channel
+            .exchange_declare(
+                exchange_name,
+                ExchangeKind::Topic,
+                ExchangeDeclareOptions {
+                    passive: false,
+                    durable: false,
+                    auto_delete: true,
+                    internal: false,
+                    nowait: false,
+                },
+                args
+            )
+            .await
+            .expect("Exchange declare failed");
+
+        let queue = channel.queue_declare(
+            routing_key,
+            QueueDeclareOptions::default(),
+            Default::default(),
+        )
+        .await
+        .expect("Queue declare failed");
+
+        let _ = channel
+            .queue_bind(
+                queue.name().as_str(),
+                exchange_name,
+                routing_key,
+                QueueBindOptions::default(),
+                FieldTable::default(),
+            )
+            .await
+            .map_err(|err| format!("error {:?}", err));
+
+        let channel = self.create_channel().await?;
+        Ok(channel)
+    }
 }
diff --git a/src/models/deployment.rs b/src/models/deployment.rs
index 3aea4e64..bfdd72b0 100644
--- a/src/models/deployment.rs
+++ b/src/models/deployment.rs
@@ -7,7 +7,7 @@ use serde_json::Value;
 pub struct Deployment {
     pub id: i32,         // id - is a unique identifier for the app project
     pub project_id: i32,  // external project ID
-    pub deleted: bool,
+    pub deleted: Option<bool>,
     pub status: String,
     pub body: Value, //json type
     pub created_at: DateTime<Utc>,
@@ -19,7 +19,7 @@ impl Deployment {
         Self {
             id: 0,
             project_id,
-            deleted: false,
+            deleted: Some(false),
             status,
             body,
             created_at: Utc::now(),
diff --git a/src/models/mod.rs b/src/models/mod.rs
index 03de8abd..c1c375bd 100644
--- a/src/models/mod.rs
+++ b/src/models/mod.rs
@@ -5,7 +5,7 @@ mod rules;
 pub mod rating;
 pub mod project;
 pub mod user;
-mod deployment;
+pub(crate) mod deployment;
 mod cloud;
 mod server;
 
diff --git a/src/models/server.rs b/src/models/server.rs
index 6098d68e..3827fcf3 100644
--- a/src/models/server.rs
+++ b/src/models/server.rs
@@ -6,7 +6,6 @@ use serde_valid::Validate;
 pub struct Server {
     pub id: i32,
     pub user_id: String,
-    pub cloud_id: i32,
     pub project_id: i32,
     #[validate(min_length = 2)]
     #[validate(max_length = 50)]
diff --git a/src/routes/cloud/update.rs b/src/routes/cloud/update.rs
index 2a69237a..3e2e564d 100644
--- a/src/routes/cloud/update.rs
+++ b/src/routes/cloud/update.rs
@@ -18,7 +18,7 @@ pub async fn item(
 ) -> Result<impl Responder> {
 
     let id = path.0;
-    let mut cloud_row = db::cloud::fetch(pg_pool.get_ref(), id)
+    let cloud_row = db::cloud::fetch(pg_pool.get_ref(), id)
         .await
         .map_err(|err| JsonResponse::<models::Cloud>::build().internal_server_error(err))
         .and_then(|cloud| match cloud {
diff --git a/src/routes/project/deploy.rs b/src/routes/project/deploy.rs
index 6bbb4cd7..a1261ab6 100644
--- a/src/routes/project/deploy.rs
+++ b/src/routes/project/deploy.rs
@@ -7,13 +7,125 @@ use crate::models;
 use actix_web::{post, web, web::Data, Responder, Result};
 use sqlx::PgPool;
 use std::sync::Arc;
+use serde_valid::Validate;
 use crate::helpers::compressor::compress;
 
 
 
-#[tracing::instrument(name = "Deploy for every user. Admin endpoint")]
-#[post("/{id}/deploy/{cloud_id}")]
+#[tracing::instrument(name = "Deploy for every user")]
+#[post("/{id}/deploy")]
 pub async fn item(
+    user: web::ReqData<Arc<models::User>>,
+    path: web::Path<(i32,)>,
+    form: web::Json<forms::project::Deploy>,
+    pg_pool: Data<PgPool>,
+    mq_manager: Data<MqManager>,
+    sets: Data<Settings>,
+) -> Result<impl Responder> {
+    let id = path.0;
+    tracing::debug!("User {:?} is deploying project: {}", user, id);
+
+    if !form.validate().is_ok() {
+        let errors = form.validate().unwrap_err().to_string();
+        let err_msg = format!("Invalid form data received {:?}", &errors);
+        tracing::debug!(err_msg);
+
+        return Err(JsonResponse::<models::Project>::build().form_error(errors));
+    }
+
+    // Validate project
+    let project = db::project::fetch(pg_pool.get_ref(), id)
+        .await
+        .map_err(|err| JsonResponse::<models::Project>::build().internal_server_error(err))
+        .and_then(|project| match project {
+            Some(project) => Ok(project),
+            None => Err(JsonResponse::<models::Project>::build().not_found("not found")),
+        })?;
+
+    // Build compose
+    let id = project.id.clone();
+    let dc = DcBuilder::new(project);
+    let fc = dc.build().map_err(|err| {
+        JsonResponse::<models::Project>::build().internal_server_error(err)
+    })?;
+
+
+    // Save cloud credentials if requested
+    let mut cloud_creds: models::Cloud = form.cloud.clone().into();
+    cloud_creds.project_id = Some(id.clone());
+    cloud_creds.user_id = user.id.clone();
+
+    if let Some(save_token) = cloud_creds.save_token {
+        if save_token {
+            db::cloud::insert(pg_pool.get_ref(), cloud_creds.clone())
+                .await
+                .map(|cloud| cloud)
+                .map_err(|_| {
+                    JsonResponse::<models::Cloud>::build().internal_server_error("Internal Server Error")
+                })?;
+        }
+    }
+
+    // Save server type and region
+    let mut server: models::Server = form.server.clone().into();
+    server.user_id = user.id.clone();
+    server.project_id = id.clone();
+    let server = db::server::insert(pg_pool.get_ref(), server)
+        .await
+        .map(|server| server)
+        .map_err(|_| {
+            JsonResponse::<models::Server>::build().internal_server_error("Internal Server Error")
+        })?;
+
+    // Build Payload for the 3-d party service through RabbitMQ
+    let mut payload = forms::project::Payload::try_from(&dc.project)
+        .map_err(|err| JsonResponse::<models::Project>::build().bad_request(err))?;
+
+    payload.server = Some(server.into());
+    payload.cloud  = Some(cloud_creds.into());
+    payload.stack  = form.stack.clone().into();
+    payload.user_token = Some(user.id.clone());
+    payload.user_email = Some(user.email.clone());
+    payload.docker_compose = Some(compress(fc.as_str()));
+
+    // Store deployment attempts into deployment table in db
+    let project_id = dc.project.id.clone();
+    let json_request = dc.project.body.clone();
+    let deployment = models::Deployment::new(
+        project_id,
+        String::from("pending"),
+        json_request
+    );
+
+    let result = db::deployment::insert(pg_pool.get_ref(), deployment)
+        .await
+        .map(|deployment| deployment)
+        .map_err(|_| {
+            JsonResponse::<models::Project>::build().internal_server_error("Internal Server Error")
+        });
+
+    tracing::debug!("Save deployment result: {:?}", result);
+    tracing::debug!("Send project data <<<<<<<<<<<>>>>>>>>>>>>>>>>{:?}", payload);
+
+    // Send Payload
+    mq_manager
+        .publish(
+            "install".to_string(),
+            "install.start.tfa.all.all".to_string(),
+            &payload,
+        )
+        .await
+        .map_err(|err| JsonResponse::<models::Project>::build().internal_server_error(err))
+        .map(|_| {
+            JsonResponse::<models::Project>::build()
+                .set_id(id)
+                .ok("Success")
+        })
+
+}
+#[tracing::instrument(name = "Deploy, when cloud token is saved")]
+#[post("/{id}/deploy/{cloud_id}")]
+pub async fn saved_item(
     user: web::ReqData<Arc<models::User>>,
     path: web::Path<(i32, i32)>,
     pg_pool: Data<PgPool>,
@@ -30,7 +142,7 @@ pub async fn item(
         .map_err(|err| JsonResponse::<models::Project>::build().internal_server_error(err))
         .and_then(|project| match project {
             Some(project) => Ok(project),
-            None => Err(JsonResponse::<models::Project>::build().not_found("not found")),
+            None => Err(JsonResponse::<models::Project>::build().not_found("Project not found")),
         })?;
 
     let id = project.id.clone();
diff --git a/src/routes/server/add.rs b/src/routes/server/add.rs
index 96ea3908..5a8970c3 100644
--- a/src/routes/server/add.rs
+++ b/src/routes/server/add.rs
@@ -1,75 +1,76 @@
-use crate::forms;
-use crate::helpers::JsonResponse;
-use crate::models;
-use crate::db;
-use actix_web::{post, web, Responder, Result};
-use sqlx::PgPool;
-use tracing::Instrument;
-use std::sync::Arc;
-use serde_valid::Validate;
+// use crate::forms;
+// use crate::helpers::JsonResponse;
+// use crate::models;
+// use crate::db;
+// use actix_web::{post, web, Responder, Result};
+// use sqlx::PgPool;
+// use tracing::Instrument;
+// use std::sync::Arc;
+// use serde_valid::Validate;
 
 // workflow
 // add, update, list, get(user_id), ACL,
 // ACL - access to func for a user
 // ACL - access to objects for a user
 
-#[tracing::instrument(name = "Add server.")]
-#[post("")]
-pub async fn add(
-    user: web::ReqData<Arc<models::User>>,
-    form: web::Json<forms::server::Server>,
-    pg_pool: web::Data<PgPool>,
-) -> Result<impl Responder> {
-
-    if !form.validate().is_ok() {
-        let errors = form.validate().unwrap_err().to_string();
-        let err_msg = format!("Invalid data received {:?}", &errors);
-        tracing::debug!(err_msg);
-
-        return Err(JsonResponse::<models::Project>::build().form_error(errors));
-    }
-
-    db::cloud::fetch(pg_pool.get_ref(), form.cloud_id)
-        .await
-        .map_err(|err| JsonResponse::<models::Cloud>::build().internal_server_error(err))
-        .and_then(|cloud| {
-            match cloud {
-                Some(cloud) if cloud.user_id != user.id => {
-                    Err(JsonResponse::<models::Cloud>::build().bad_request("Cloud not found"))
-                }
-                Some(cloud) => {
-                    Ok(cloud)
-                },
-                None => Err(JsonResponse::<models::Cloud>::build().not_found("Cloud not found"))
-            }
-        })?;
-
-    db::project::fetch(pg_pool.get_ref(), form.project_id)
-        .await
-        .map_err(|_err| JsonResponse::<models::Server>::build()
-            .bad_request("Invalid project"))
-        .and_then(|project| {
-            match project {
-                Some(project) if project.user_id != user.id => {
-                    Err(JsonResponse::<models::Project>::build().bad_request("Project not found"))
-                }
-                Some(project) => { Ok(project) },
-                None => Err(JsonResponse::<models::Server>::build().not_found("Project not found"))
-            }
-        })?;
-
-    let mut server: models::Server = form.into_inner().into();
-    server.user_id = user.id.clone();
-
-    db::server::insert(pg_pool.get_ref(), server)
-        .await
-        .map(|server| JsonResponse::build()
-            .set_item(server)
-            .ok("success"))
-        .map_err(|err|
-            match err {
-                _ => {
-                    return JsonResponse::<models::Server>::build().internal_server_error("Failed to insert");
-                }
-            })
-}
+// #[tracing::instrument(name = "Add server.")]
+// #[post("")]
+// pub async fn add(
+//     user: web::ReqData<Arc<models::User>>,
+//     form: web::Json<forms::server::Server>,
+//     pg_pool: web::Data<PgPool>,
+// ) -> Result<impl Responder> {
+// //
+// //     if !form.validate().is_ok() {
+// //         let errors = form.validate().unwrap_err().to_string();
+// //         let err_msg = format!("Invalid data received {:?}", &errors);
+// //         tracing::debug!(err_msg);
+// //
+// //         return Err(JsonResponse::<models::Project>::build().form_error(errors));
+// //     }
+// //
+// //
+// //     db::cloud::fetch(pg_pool.get_ref(), form.cloud_id)
+// //         .await
+// //         .map_err(|err| JsonResponse::<models::Cloud>::build().internal_server_error(err))
+// //         .and_then(|cloud| {
+// //             match cloud {
+// //                 Some(cloud) if cloud.user_id != user.id => {
+// //                     Err(JsonResponse::<models::Cloud>::build().bad_request("Cloud not found"))
+// //                 }
+// //                 Some(cloud) => {
+// //                     Ok(cloud)
+// //                 },
+// //                 None => Err(JsonResponse::<models::Cloud>::build().not_found("Cloud not found"))
+// //             }
+// //         })?;
+// //
+// //     db::project::fetch(pg_pool.get_ref(), form.project_id)
+// //         .await
+// //         .map_err(|_err| JsonResponse::<models::Server>::build()
+// //             .bad_request("Invalid project"))
+// //         .and_then(|project| {
+// //             match project {
+// //                 Some(project) if project.user_id != user.id => {
+// //                     Err(JsonResponse::<models::Project>::build().bad_request("Project not found"))
+// //                 }
+// //                 Some(project) => { Ok(project) },
+// //                 None => Err(JsonResponse::<models::Server>::build().not_found("Project not found"))
+// //             }
+// //         })?;
+// //
+// //     let mut server: models::Server = form.into_inner().into();
+// //     server.user_id = user.id.clone();
+// //
+// //     db::server::insert(pg_pool.get_ref(), server)
+// //         .await
+// //         .map(|server| JsonResponse::build()
+// //             .set_item(server)
+// //             .ok("success"))
+// //         .map_err(|err|
+// //             match err {
+// //                 _ => {
+// //                     return JsonResponse::<models::Server>::build().internal_server_error("Failed to insert");
+// //                 }
+// //             })
+// }
diff --git a/src/routes/server/mod.rs b/src/routes/server/mod.rs
index 319c89fb..af796d2d 100644
--- a/src/routes/server/mod.rs
+++ b/src/routes/server/mod.rs
@@ -1,7 +1,7 @@
 pub mod add;
 pub(crate) mod get;
 pub(crate) mod delete;
-mod update;
+pub(crate) mod update;
 
 pub use get::*;
 pub use add::*;
diff --git a/src/routes/server/update.rs b/src/routes/server/update.rs
index b7087781..62208698 100644
--- a/src/routes/server/update.rs
+++ b/src/routes/server/update.rs
@@ -35,7 +35,6 @@ pub async fn item(
 
     let mut server:models::Server = form.into_inner().into();
     server.id = server_row.id;
-    server.cloud_id = server_row.cloud_id;
     server.project_id = server_row.project_id;
     server.user_id = user.id.clone();
     // exclude
diff --git a/src/startup.rs b/src/startup.rs
index 612d3219..2c7c4baa 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -100,8 +100,8 @@ pub async fn run(
                     .wrap(Cors::permissive())
                     .service(crate::routes::server::get::item)
                     .service(crate::routes::server::get::list)
-                    .service(crate::routes::server::add::add)
-                    // .service(crate::routes::server::update::item)
+                    // .service(crate::routes::server::add::add)
+                    .service(crate::routes::server::update::item)
                     .service(crate::routes::server::delete::item),
             )
             .app_data(pg_pool.clone())
diff --git a/tests/mock_data/deploy.json b/tests/mock_data/deploy.json
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/mock_data/deploy2.json b/tests/mock_data/deploy2.json
new file mode 100644
index 00000000..ed30b57b
--- /dev/null
+++ b/tests/mock_data/deploy2.json
@@ -0,0 +1 @@
+{"cloud":{"save_token":false,"cloud_token":"4CVzsUUjW3ecn3djvXVC9HZI27QK3jNONRWyMB57c4p45Mp9m0rFUR95LwbYv1ov","provider":"htz"},"server":{"region":"fsn1","zone":null,"server":"cx11","os":"ubuntu-20.04","disk_type":"pd-standart","servers_count":3},"stack":{"vars":[],"integrated_features":[],"extended_features":[],"subscriptions":[],"form_app":[]}}

From 0cf61053068bf4115fdd9a45ac0b0b4d35311075 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Sun, 17 Mar 2024 09:33:35 +0200
Subject: [PATCH 217/284] mq listener, added futures_lite::stream::StraemExt

---
 Cargo.lock                          |  2 +-
 Cargo.toml                          |  2 +-
 src/console/commands/mq/listener.rs | 11 ++++++-----
 src/helpers/mq_manager.rs           |  4 ++--
 tests/mock_data/cloud-update.json   |  1 +
 tests/mock_data/cloud.json          |  1 +
 tests/mock_data/deploy.json         |  1 +
 tests/mock_data/deploy2.json        |  2 +-
 tests/mock_data/project-update.json |  3 ++-
 tests/mock_data/project.json        |  3 +--
 tests/mock_data/server-update.json  |  4 ++--
 tests/mock_data/server.json         |  6 +++---
 12 files changed, 22 insertions(+), 18 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 0ea5e67f..1f08eb2f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3450,7 +3450,7 @@ dependencies = [
  "derive_builder 0.12.0",
  "docker-compose-types",
  "futures",
- "futures-lite 1.13.0",
+ "futures-lite 2.2.0",
  "futures-util",
  "glob",
  "hmac",
diff --git a/Cargo.toml b/Cargo.toml
index f817a308..b7daae6a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -52,7 +52,7 @@ derive_builder = "0.12.0"
 indexmap = { version = "2.0.0", features = ["serde"], optional = true }
 serde_yaml = "0.9"
 lapin = { version = "2.3.1", features = ["serde_json"] }
-futures-lite = "1.13.0"
+futures-lite = "2.2.0"
 clap = { version = "4.4.8", features = ["derive"] }
 brotli = "3.4.0"
 serde_path_to_error = "0.1.14"
diff --git a/src/console/commands/mq/listener.rs b/src/console/commands/mq/listener.rs
index 75cae061..cfe329a3 100644
--- a/src/console/commands/mq/listener.rs
+++ b/src/console/commands/mq/listener.rs
@@ -9,6 +9,7 @@ use db::deployment;
 use crate::{db, helpers};
 use crate::helpers::mq_manager;
 use crate::helpers::mq_manager::MqManager;
+use futures_lite::stream::StreamExt;
 
 pub struct ListenCommand {
 }
@@ -39,7 +40,7 @@ impl crate::console::commands::CallableTrait for ListenCommand {
                 .await?;
 
 
-            let consumer = consumer_channel
+            let mut consumer = consumer_channel
                 .basic_consume(
                     "install_progress",
                     "console_listener",
@@ -53,10 +54,10 @@ impl crate::console::commands::CallableTrait for ListenCommand {
 
             tracing::info!("will consume");
             // if let Ok(consumer) = consumer {
-            //     while let Some(delivery) = consumer.next().await {
-            //         let delivery = delivery.expect("error in consumer");
-            //         delivery.ack(BasicAckOptions::default()).await.expect("ack");
-            //     }
+                while let Some(delivery) = consumer.next().await {
+                    let delivery = delivery.expect("error in consumer");
+                    delivery.ack(BasicAckOptions::default()).await.expect("ack");
+                }
             // }
 
             // while let Some(delivery) = consumer.next().await {
diff --git a/src/helpers/mq_manager.rs b/src/helpers/mq_manager.rs
index a7eb5e57..c2658251 100644
--- a/src/helpers/mq_manager.rs
+++ b/src/helpers/mq_manager.rs
@@ -117,8 +117,8 @@ impl MqManager {
                 ExchangeKind::Topic,
                 ExchangeDeclareOptions {
                     passive: false,
-                    durable: false,
-                    auto_delete: true,
+                    durable: true,
+                    auto_delete: false,
                     internal: false,
                     nowait: false,
                 },
diff --git a/tests/mock_data/cloud-update.json b/tests/mock_data/cloud-update.json
index d436baa1..72967ad9 100644
--- a/tests/mock_data/cloud-update.json
+++ b/tests/mock_data/cloud-update.json
@@ -1,5 +1,6 @@
 {
   "user_id": "hy181TZa4DaabUZWklsrxw",
+  "project_id": 1,
   "provider": "htz",
   "cloud_token": "cloud_token_updates",
   "cloud_key": "cloud_token_updates",
diff --git a/tests/mock_data/cloud.json b/tests/mock_data/cloud.json
index 9e680c90..04f74125 100644
--- a/tests/mock_data/cloud.json
+++ b/tests/mock_data/cloud.json
@@ -1,5 +1,6 @@
 {
   "user_id": "hy181TZa4DaabUZWklsrxw",
+  "project_id": 1,
   "provider": "htz",
   "cloud_token": "cloud_token_here",
   "cloud_key": "cloud_token_here",
diff --git a/tests/mock_data/deploy.json b/tests/mock_data/deploy.json
index e69de29b..6917d31b 100644
--- a/tests/mock_data/deploy.json
+++ b/tests/mock_data/deploy.json
@@ -0,0 +1 @@
+{"project":{"networks":[{"id":"ltoi393j2i4iit2pz","name":"default_network"}],"web":[{"_created":"2023-05-10T09:57:23.773552","_etag":null,"_id":"ltoi3u3515z6nwsk1","_updated":"2024-02-27T15:10:40.107999","ansible_var":null,"autodeploy":null,"avoid_render":null,"category":[null],"category_id":null,"code":"openresty","commercial":null,"cpu":"0.0","custom_preset":{"dockerhub_name":"openresty","dockerhub_user":"openresty","environment":[],"restart":"always","shared_ports":[],"volumes":[]},"default":true,"dependency":null,"descr":null,"description":"<p class=\"content-editor__unstyled\">a dynamic web platform built on NGINX and LuaJIT. <a class=\"draft-link\" href=\"https://hub.docker.com/r/openresty/openresty\" target=\"_blank\" rel=\"nofollow\">Learn more<i class=\"fas fa-external-link-alt draft-link__external-icon\"></i></a></p>","disk_size":null,"docker_image_is_internal":true,"dockerhub_image":"openresty","dockerhub_name":"openresty","dockerhub_user":"openresty","domain":"","environment":[],"form":null,"full_description":null,"group":[],"icon":{"dark":{},"light":{"height":150,"image":"12140d93-350c-4fb8-b3d2-e350f3943b0b.svg","width":147}},"links":[{"follow":false,"title":"Openresty","type":"vendor","url":"https://openresty.org/"},{"follow":false,"repo_name":"openresty","repo_owner":"openresty","type":"github"}],"name":"OpenResty","network":["ltoi393j2i4iit2pz"],"parent_app_id":null,"plan_type":null,"popularity":null,"ports":{"public":["80","443"]},"price":null,"ram_size":null,"repo_dir":null,"requirements":null,"restart":"always","role":null,"shared_ports":[{"host_port":"80","container_port":"80"}],"subscription":null,"suggested":null,"timestamp":"2024-03-12T15:02:14.033Z","type":"web","version":{"_id":586,"name":"1.15.8.3","tag":"1.15.8.3","update_status":"published","version":"1.15.8.3"},"versions":[{"_id":586,"name":"1.15.8.3","tag":"1.15.8.3","update_status":"published","version":"1.15.8.3"}],"volumes":[]}],"feature":[],"service":[],"custom_stack_category":null,"custom_stack_code":"project-1","custom_stack_description":null,"custom_stack_short_description":null,"project_description":null,"project_git_url":null,"project_name":"Project 1","project_overview":null},"cloud":{"provider": "htz","save_token":true,"cloud_token":"*****"},"server":{"region":"fsn1","zone":null,"server":"cx11","os":"ubuntu-20.04","disk_type":"pd-standart","servers_count":3},"stack":{"commonDomain":"","domainList":{},"ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":[],"form_app":[]}}
\ No newline at end of file
diff --git a/tests/mock_data/deploy2.json b/tests/mock_data/deploy2.json
index ed30b57b..4bf38579 100644
--- a/tests/mock_data/deploy2.json
+++ b/tests/mock_data/deploy2.json
@@ -1 +1 @@
-{"cloud":{"save_token":false,"cloud_token":"4CVzsUUjW3ecn3djvXVC9HZI27QK3jNONRWyMB57c4p45Mp9m0rFUR95LwbYv1ov","provider":"htz"},"server":{"region":"fsn1","zone":null,"server":"cx11","os":"ubuntu-20.04","disk_type":"pd-standart","servers_count":3},"stack":{"vars":[],"integrated_features":[],"extended_features":[],"subscriptions":[],"form_app":[]}}
+{"cloud":{"save_token":false,"cloud_token":"****","provider":"htz"},"server":{"region":"fsn1","zone":null,"server":"cx11","os":"ubuntu-20.04","disk_type":"pd-standart","servers_count":3},"stack":{"vars":[],"integrated_features":[],"extended_features":[],"subscriptions":[],"form_app":[]}}
diff --git a/tests/mock_data/project-update.json b/tests/mock_data/project-update.json
index 4c0e7c7c..f13f68be 100644
--- a/tests/mock_data/project-update.json
+++ b/tests/mock_data/project-update.json
@@ -1,6 +1,7 @@
 {
+  "id": 1,
   "stack_id": "9239ea1d-8306-4493-aae1-fcc00de76241",
   "user_id": "hy181TZa4DaabUZWklsrxw",
   "name": "sample",
-  "body": "{}"
+  "body": "{\"key\": \"val\"}"
 }
\ No newline at end of file
diff --git a/tests/mock_data/project.json b/tests/mock_data/project.json
index 9ff62aa5..4c0e7c7c 100644
--- a/tests/mock_data/project.json
+++ b/tests/mock_data/project.json
@@ -2,6 +2,5 @@
   "stack_id": "9239ea1d-8306-4493-aae1-fcc00de76241",
   "user_id": "hy181TZa4DaabUZWklsrxw",
   "name": "sample",
-  "body": "{}",
-  "cloud_id": 1
+  "body": "{}"
 }
\ No newline at end of file
diff --git a/tests/mock_data/server-update.json b/tests/mock_data/server-update.json
index 4ad21734..b85eb429 100644
--- a/tests/mock_data/server-update.json
+++ b/tests/mock_data/server-update.json
@@ -1,6 +1,7 @@
 {
   "id": 1,
   "user_id": "hy181TZa4DaabUZWklsrxw",
+  "project_id": 1,
   "cloud_id": 1,
   "region": "fra-1",
   "zone": "a",
@@ -8,7 +9,6 @@
   "os": "3408230498203948234",
   "disk_type": "samples",
   "created_at": "",
-  "updated_at": "",
-  "project_id": 1
+  "updated_at": ""
 }
 
diff --git a/tests/mock_data/server.json b/tests/mock_data/server.json
index cec56c91..2d7d6269 100644
--- a/tests/mock_data/server.json
+++ b/tests/mock_data/server.json
@@ -1,13 +1,13 @@
 {
   "user_id": "hy181TZa4DaabUZWklsrxw",
-  "cloud_id": 3,
+  "project_id":1,
+  "cloud_id": 1,
   "region": "fra-1",
   "zone": "a",
   "server": "server-1",
   "os": "3408230498203948234",
   "disk_type": "samples",
   "created_at": "",
-  "updated_at": "",
-  "project_id":4
+  "updated_at": ""
 }
 

From 9450ef4802906e91ebe80d19c8be3644904bdc7c Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Sun, 17 Mar 2024 10:32:50 +0200
Subject: [PATCH 218/284] slight refactoring, deref() for subforms

---
 src/forms/cloud.rs           | 12 ++++++------
 src/forms/server.rs          | 12 ++++++------
 src/routes/cloud/add.rs      |  3 ++-
 src/routes/cloud/update.rs   |  3 ++-
 src/routes/project/deploy.rs | 32 ++++++++++++++------------------
 src/routes/server/update.rs  |  3 ++-
 6 files changed, 32 insertions(+), 33 deletions(-)

diff --git a/src/forms/cloud.rs b/src/forms/cloud.rs
index 138b2989..472bdb70 100644
--- a/src/forms/cloud.rs
+++ b/src/forms/cloud.rs
@@ -15,14 +15,14 @@ pub struct Cloud {
     pub save_token: Option<bool>,
 }
 
-impl Into<models::Cloud> for Cloud {
+impl Into<models::Cloud> for &Cloud {
     fn into(self) -> models::Cloud {
         let mut cloud = models::Cloud::default();
-        cloud.provider = self.provider;
-        cloud.cloud_token = self.cloud_token;
-        cloud.cloud_key = self.cloud_key;
-        cloud.cloud_secret = self.cloud_secret;
-        cloud.save_token = self.save_token;
+        cloud.provider = self.provider.clone();
+        cloud.cloud_token = self.cloud_token.clone();
+        cloud.cloud_key = self.cloud_key.clone();
+        cloud.cloud_secret = self.cloud_secret.clone();
+        cloud.save_token = self.save_token.clone();
 
         cloud
     }
diff --git a/src/forms/server.rs b/src/forms/server.rs
index 1e55a895..cba997f9 100644
--- a/src/forms/server.rs
+++ b/src/forms/server.rs
@@ -14,14 +14,14 @@ pub struct Server {
     pub disk_type: Option<String>,
 }
 
-impl Into<models::Server> for Server {
+impl Into<models::Server> for &Server {
     fn into(self) -> models::Server {
         let mut server = models::Server::default();
-        server.disk_type = self.disk_type;
-        server.region = self.region;
-        server.server = self.server;
-        server.zone = self.zone;
-        server.os = self.os;
+        server.disk_type = self.disk_type.clone();
+        server.region = self.region.clone();
+        server.server = self.server.clone();
+        server.zone = self.zone.clone();
+        server.os = self.os.clone();
         server.created_at = Utc::now();
         server.updated_at = Utc::now();
 
diff --git a/src/routes/cloud/add.rs b/src/routes/cloud/add.rs
index cb572096..6178ff1f 100644
--- a/src/routes/cloud/add.rs
+++ b/src/routes/cloud/add.rs
@@ -1,3 +1,4 @@
+use std::ops::Deref;
 use crate::forms;
 use crate::helpers::JsonResponse;
 use crate::models;
@@ -29,7 +30,7 @@ pub async fn add(
         return Err(JsonResponse::<models::Project>::build().form_error(errors));
     }
 
-    let mut cloud: models::Cloud = form.into_inner().into();
+    let mut cloud: models::Cloud = form.deref().into();
     cloud.user_id = user.id.clone();
 
     db::cloud::insert(pg_pool.get_ref(), cloud)
diff --git a/src/routes/cloud/update.rs b/src/routes/cloud/update.rs
index 3e2e564d..b48fd8bc 100644
--- a/src/routes/cloud/update.rs
+++ b/src/routes/cloud/update.rs
@@ -7,6 +7,7 @@ use serde_valid::Validate;
 use sqlx::PgPool;
 use std::sync::Arc;
 use tracing::Instrument;
+use std::ops::Deref;
 
 #[tracing::instrument(name = "Update cloud.")]
 #[put("/{id}")]
@@ -33,7 +34,7 @@ pub async fn item(
         return Err(JsonResponse::<models::Cloud>::build().form_error(errors.to_string()));
     }
 
-    let mut cloud:models::Cloud = form.into_inner().into();
+    let mut cloud:models::Cloud = form.deref().into();
     cloud.id = cloud_row.id;
     cloud.user_id = user.id.clone();
 
diff --git a/src/routes/project/deploy.rs b/src/routes/project/deploy.rs
index a1261ab6..b0665d8f 100644
--- a/src/routes/project/deploy.rs
+++ b/src/routes/project/deploy.rs
@@ -43,7 +43,7 @@ pub async fn item(
         })?;
 
     // Build compose
-    let id = project.id.clone();
+    let id = project.id;
     let dc = DcBuilder::new(project);
     let fc = dc.build().map_err(|err| {
         JsonResponse::<models::Project>::build().internal_server_error(err)
@@ -51,25 +51,23 @@ pub async fn item(
 
 
     // Save cloud credentials if requested
-    let mut cloud_creds: models::Cloud = form.cloud.clone().into();
-    cloud_creds.project_id = Some(id.clone());
+    let mut cloud_creds: models::Cloud = (&form.cloud).into();
+    cloud_creds.project_id = Some(id);
     cloud_creds.user_id = user.id.clone();
 
-    if let Some(save_token) = cloud_creds.save_token {
-        if save_token {
-            db::cloud::insert(pg_pool.get_ref(), cloud_creds.clone())
-                .await
-                .map(|cloud| cloud)
-                .map_err(|_| {
-                    JsonResponse::<models::Cloud>::build().internal_server_error("Internal Server Error")
-                })?;
-        }
+    if Some(true) == cloud_creds.save_token {
+        db::cloud::insert(pg_pool.get_ref(), cloud_creds.clone())
+            .await
+            .map(|cloud| cloud)
+            .map_err(|_| {
+                JsonResponse::<models::Cloud>::build().internal_server_error("Internal Server Error")
+            })?;
     }
 
     // Save server type and region
-    let mut server: models::Server = form.server.clone().into();
+    let mut server: models::Server = (&form.server).into();
     server.user_id = user.id.clone();
-    server.project_id = id.clone();
+    server.project_id = id;
     let server = db::server::insert(pg_pool.get_ref(), server)
         .await
         .map(|server| server)
@@ -89,10 +87,9 @@ pub async fn item(
     payload.docker_compose = Some(compress(fc.as_str()));
 
     // Store deployment attempts into deployment table in db
-    let project_id = dc.project.id.clone();
     let json_request = dc.project.body.clone();
     let deployment = models::Deployment::new(
-        project_id,
+        dc.project.id,
         String::from("pending"),
         json_request
     );
@@ -189,10 +186,9 @@ pub async fn saved_item(
     payload.docker_compose = Some(compress(fc.as_str()));
 
 
-    let project_id = dc.project.id.clone();
     let json_request = dc.project.body.clone();
     let deployment = models::Deployment::new(
-        project_id,
+        dc.project.id,
         String::from("pending"),
         json_request
     );
diff --git a/src/routes/server/update.rs b/src/routes/server/update.rs
index 62208698..02c4b0eb 100644
--- a/src/routes/server/update.rs
+++ b/src/routes/server/update.rs
@@ -7,6 +7,7 @@ use serde_valid::Validate;
 use sqlx::PgPool;
 use std::sync::Arc;
 use tracing::Instrument;
+use std::ops::Deref;
 
 #[tracing::instrument(name = "Update server.")]
 #[put("/{id}")]
@@ -33,7 +34,7 @@ pub async fn item(
         return Err(JsonResponse::<models::Server>::build().form_error(errors.to_string()));
     }
 
-    let mut server:models::Server = form.into_inner().into();
+    let mut server:models::Server = form.deref().into();
     server.id = server_row.id;
     server.project_id = server_row.project_id;
     server.user_id = user.id.clone();

From f665d5de1fed92de9425dbf5ee9bb53a69027980 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Wed, 20 Mar 2024 15:04:05 +0200
Subject: [PATCH 219/284] mq listener, read queue and update status

---
 Dockerfile                          |  3 +-
 docker-compose.yml                  | 16 ++++++
 docker/dev/docker-compose.yml       | 20 +++++++
 src/console/commands/mq/listener.rs | 86 ++++++++++++++++++-----------
 src/console/main.rs                 |  3 +-
 src/db/deployment.rs                | 26 +++++++++
 src/helpers/mq_manager.rs           | 19 ++++++-
 src/routes/project/deploy.rs        |  1 -
 8 files changed, 135 insertions(+), 39 deletions(-)

diff --git a/Dockerfile b/Dockerfile
index 34fa4b03..66222d1e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -37,7 +37,7 @@ COPY ./src ./src
 #    cargo build --release
 
 RUN apt-get update && apt-get install --no-install-recommends -y libssl-dev; \
-    cargo build --release
+    cargo build --bin=console && cargo build --release
 
 #RUN ls -la /app/target/release/ >&2
 
@@ -51,6 +51,7 @@ RUN mkdir ./files && chmod 0777 ./files
 
 # copy binary and configuration files
 COPY --from=builder /app/target/release/server .
+COPY --from=builder /app/target/release/console .
 COPY --from=builder /app/.env .
 COPY --from=builder /app/configuration.yaml .
 COPY --from=builder /usr/local/cargo/bin/sqlx sqlx
diff --git a/docker-compose.yml b/docker-compose.yml
index e9aee75a..f87b3e27 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -29,6 +29,22 @@ services:
 #      stackerdb:
 #        condition: service_healthy
 
+  stacker_queue:
+    image: trydirect/stacker:0.0.7
+    container_name: stacker_queue
+    restart: always
+    volumes:
+      - ./configuration.yaml:/app/configuration.yaml
+      - ./.env:/app/.env
+    environment:
+      - RUST_LOG=debug
+      - RUST_BACKTRACE=1
+    env_file:
+      - ./.env
+    depends_on:
+      stackerdb:
+        condition: service_healthy
+    entrypoint: /app/console mq listen
 
 #  stackerdb:
 #    container_name: stackerdb
diff --git a/docker/dev/docker-compose.yml b/docker/dev/docker-compose.yml
index 6bf8eb55..3577b145 100644
--- a/docker/dev/docker-compose.yml
+++ b/docker/dev/docker-compose.yml
@@ -37,6 +37,26 @@ services:
       - backend
 
 
+  stacker_queue:
+    image: trydirect/stacker:0.0.7
+    container_name: stacker_queue
+    restart: always
+    volumes:
+      - ./configuration.yaml:/app/configuration.yaml
+      - ./.env:/app/.env
+    environment:
+      - RUST_LOG=debug
+      - RUST_BACKTRACE=1
+    env_file:
+      - ./.env
+    depends_on:
+      stackerdb:
+        condition: service_healthy
+    entrypoint: /app/console mq listen
+    networks:
+      - backend
+
+
   stackerdb:
     container_name: stackerdb
     healthcheck:
diff --git a/src/console/commands/mq/listener.rs b/src/console/commands/mq/listener.rs
index cfe329a3..501d1991 100644
--- a/src/console/commands/mq/listener.rs
+++ b/src/console/commands/mq/listener.rs
@@ -1,19 +1,31 @@
 use crate::configuration::get_configuration;
 use actix_web::rt;
 use actix_web::web;
+use chrono::Utc;
 use lapin::{Channel, Queue};
 use lapin::options::{BasicAckOptions, BasicConsumeOptions};
 use lapin::types::FieldTable;
 use sqlx::PgPool;
 use db::deployment;
-use crate::{db, helpers};
-use crate::helpers::mq_manager;
+use crate::{db, forms, helpers};
+use crate::helpers::{JsonResponse, mq_manager};
 use crate::helpers::mq_manager::MqManager;
 use futures_lite::stream::StreamExt;
+use serde_derive::{Deserialize, Serialize};
+use crate::forms::project::ProjectForm;
 
 pub struct ListenCommand {
 }
 
+#[derive(Serialize, Deserialize, Debug)]
+struct ProgressMessage {
+    alert: i32,
+    id: String,
+    message: String,
+    status: String,
+    progress: String
+}
+
 impl ListenCommand {
     pub fn new() -> Self {
         Self {}
@@ -31,18 +43,20 @@ impl crate::console::commands::CallableTrait for ListenCommand {
 
             let db_pool = web::Data::new(db_pool);
 
+            println!("Declare exchange");
             let mq_manager = MqManager::try_new(settings.amqp.connection_string())?;
             let consumer_channel= mq_manager
                 .consume(
                     "install_progress",
-                    "install_progress_*******"
+                    "install.progress.#"
                 )
                 .await?;
 
 
+            println!("Declare queue");
             let mut consumer = consumer_channel
                 .basic_consume(
-                    "install_progress",
+                    "#",
                     "console_listener",
                     BasicConsumeOptions::default(),
                     FieldTable::default(),
@@ -50,38 +64,46 @@ impl crate::console::commands::CallableTrait for ListenCommand {
                 .await
                 .expect("Basic consume");
 
-                // .map_err(|err| format!("Error {:?}", err));
+            println!("Waiting for messages ..");
+            while let Some(delivery) = consumer.next().await {
+                // println!("checking messages delivery {:?}", delivery);
+                let delivery = delivery.expect("error in consumer");
+                let s:String = match String::from_utf8(delivery.data.to_owned()) {
+                    //delivery.data is of type Vec<u8>
+                    Ok(v) => v,
+                    Err(e) => panic!("Invalid UTF-8 sequence: {}", e),
+                };
 
-            tracing::info!("will consume");
-            // if let Ok(consumer) = consumer {
-                while let Some(delivery) = consumer.next().await {
-                    let delivery = delivery.expect("error in consumer");
-                    delivery.ack(BasicAckOptions::default()).await.expect("ack");
-                }
-            // }
+                match serde_json::from_str::<ProgressMessage>(&s) {
+                    Ok(msg) => {
+                        println!("message {:?}", msg);
+                        // println!("id {:?}", msg.id);
+                        // println!("status {:?}", msg.status);
+                        delivery.ack(BasicAckOptions::default()).await.expect("ack");
 
-            // while let Some(delivery) = consumer.next().await {
-            //     tracing::debug!(message=?delivery, "received message");
-            //     if let Ok(delivery) = delivery {
-            //         delivery
-            //             .ack(BasicAckOptions::default())
-            //             .await
-            //             .expect("basic_ack");
-            //     }
-            // }
+                        if msg.status == "complete" {
+                            let id = msg.id
+                                .parse::<i32>()
+                                .map_err(|err| "Could not parse deployment id".to_string() )?;
 
+                            match crate::db::deployment::fetch(
+                                db_pool.get_ref(), id
+                            )
+                            .await? {
 
-            // on_complete()
-            // let deployment = crate::models::deployment::Deployment {
-            //     id: 0,
-            //     project_id: 0,
-            //     deleted: false,
-            //     status: "".to_string(),
-            //     body: Default::default(),
-            //     created_at: Default::default(),
-            //     updated_at: Default::default(),
-            // };
-            // deployment::update(db_pool.get_ref(), deployment).await?;
+                                Some(mut row) => {
+                                    row.status = msg.status;
+                                    row.updated_at = Utc::now();
+                                    deployment::update(db_pool.get_ref(), row).await?;
+                                    println!("deployment {} completed successfully", id);
+                                }
+                                None => println!("Deployment record not found in db")
+                            }
+                        }
+                    }
+                    Err(err) => { tracing::debug!("Invalid message format")}
+                }
+            }
 
             Ok(())
         })
diff --git a/src/console/main.rs b/src/console/main.rs
index 752e10ae..f6bc804e 100644
--- a/src/console/main.rs
+++ b/src/console/main.rs
@@ -52,7 +52,6 @@ fn get_command(cli: Cli) -> Result<Box<dyn stacker::console::commands::CallableT
             AppMqCommands::Listen {} => Ok(Box::new(
                 stacker::console::commands::mq::ListenCommand::new(),
             )),
-        },
-        _ => Err("command does not match".to_string()),
+        }
     }
 }
diff --git a/src/db/deployment.rs b/src/db/deployment.rs
index 11e7a2e3..7f78f0c2 100644
--- a/src/db/deployment.rs
+++ b/src/db/deployment.rs
@@ -2,6 +2,32 @@ use crate::models;
 use sqlx::PgPool;
 use tracing::Instrument;
 
+
+pub async fn fetch(pool: &PgPool, id: i32) -> Result<Option<models::Deployment>, String> {
+    tracing::info!("Fetch deployment {}", id);
+    sqlx::query_as!(
+        models::Deployment,
+        r#"
+        SELECT
+            *
+        FROM deployment
+        WHERE id=$1
+        LIMIT 1
+        "#,
+        id
+    )
+        .fetch_one(pool)
+        .await
+        .map(|deployment| Some(deployment))
+        .or_else(|err| match err {
+            sqlx::Error::RowNotFound => Ok(None),
+            e => {
+                tracing::error!("Failed to fetch deployment, error: {:?}", e);
+                Err("Could not fetch data".to_string())
+            }
+        })
+}
+
 pub async fn insert(pool: &PgPool, mut deployment: models::Deployment) -> Result<models::Deployment, String> {
     let query_span = tracing::info_span!("Saving new deployment into the database");
     sqlx::query!(
diff --git a/src/helpers/mq_manager.rs b/src/helpers/mq_manager.rs
index c2658251..f30d2385 100644
--- a/src/helpers/mq_manager.rs
+++ b/src/helpers/mq_manager.rs
@@ -4,6 +4,7 @@ use lapin::{options::*, publisher_confirm::{Confirmation, PublisherConfirm}, Bas
 use lapin::types::AMQPType::ShortString;
 use lapin::types::{AMQPValue, FieldTable};
 use serde::ser::Serialize;
+use serde_valid::validation::error::Format::Default;
 
 #[derive(Debug)]
 pub struct MqManager {
@@ -127,10 +128,22 @@ impl MqManager {
             .await
             .expect("Exchange declare failed");
 
+        let mut args = FieldTable::default();
+        args.insert("x-expires".into(), AMQPValue::LongUInt(180000));
+
         let queue = channel.queue_declare(
-            routing_key,
-            QueueDeclareOptions::default(),
-            Default::default(),
+            // "install_progress_all",
+            "#",
+            // "install_progress_hy181TZa4DaabUZWklsrxw",
+            QueueDeclareOptions {
+                passive: false,
+                durable: false,
+                exclusive: false,
+                auto_delete: true,
+                nowait: false,
+            },
+            // FieldTable::default(),
+            args,
         )
         .await
         .expect("Queue declare failed");
diff --git a/src/routes/project/deploy.rs b/src/routes/project/deploy.rs
index b0665d8f..9844035f 100644
--- a/src/routes/project/deploy.rs
+++ b/src/routes/project/deploy.rs
@@ -49,7 +49,6 @@ pub async fn item(
         JsonResponse::<models::Project>::build().internal_server_error(err)
     })?;
 
-
     // Save cloud credentials if requested
     let mut cloud_creds: models::Cloud = (&form.cloud).into();
     cloud_creds.project_id = Some(id);

From d4d21b441d3aae4d232d34c88d67fb100bbe5773 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Wed, 20 Mar 2024 15:13:24 +0200
Subject: [PATCH 220/284] mq listener, read queue and update status

---
 Dockerfile                          |  3 +-
 access_control.conf.dist            |  2 +-
 docker-compose.yml                  | 16 ++++++
 docker/dev/docker-compose.yml       | 20 +++++++
 src/console/commands/mq/listener.rs | 86 ++++++++++++++++++-----------
 src/console/main.rs                 |  3 +-
 src/db/deployment.rs                | 26 +++++++++
 src/forms/cloud.rs                  | 12 ++--
 src/forms/mod.rs                    |  1 +
 src/forms/server.rs                 | 12 ++--
 src/forms/user.rs                   |  4 +-
 src/helpers/mq_manager.rs           | 19 ++++++-
 src/routes/cloud/add.rs             |  3 +-
 src/routes/cloud/update.rs          |  3 +-
 src/routes/project/deploy.rs        | 33 +++++------
 src/routes/server/update.rs         |  3 +-
 src/startup.rs                      |  1 -
 tests/middleware_trydirect.rs       |  4 --
 18 files changed, 172 insertions(+), 79 deletions(-)

diff --git a/Dockerfile b/Dockerfile
index 34fa4b03..66222d1e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -37,7 +37,7 @@ COPY ./src ./src
 #    cargo build --release
 
 RUN apt-get update && apt-get install --no-install-recommends -y libssl-dev; \
-    cargo build --release
+    cargo build --bin=console && cargo build --release
 
 #RUN ls -la /app/target/release/ >&2
 
@@ -51,6 +51,7 @@ RUN mkdir ./files && chmod 0777 ./files
 
 # copy binary and configuration files
 COPY --from=builder /app/target/release/server .
+COPY --from=builder /app/target/release/console .
 COPY --from=builder /app/.env .
 COPY --from=builder /app/configuration.yaml .
 COPY --from=builder /usr/local/cargo/bin/sqlx sqlx
diff --git a/access_control.conf.dist b/access_control.conf.dist
index 459a15fc..f164af1a 100644
--- a/access_control.conf.dist
+++ b/access_control.conf.dist
@@ -11,4 +11,4 @@ g = _, _
 e = some(where (p.eft == allow))
 
 [matchers]
-m = g(r.sub, p.sub) && r.obj == p.obj && regexMatch(r.act, p.act)
+m = g(r.sub, p.sub) && keyMatch2(r.obj, p.obj) && r.act == p.act
diff --git a/docker-compose.yml b/docker-compose.yml
index e9aee75a..f87b3e27 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -29,6 +29,22 @@ services:
 #      stackerdb:
 #        condition: service_healthy
 
+  stacker_queue:
+    image: trydirect/stacker:0.0.7
+    container_name: stacker_queue
+    restart: always
+    volumes:
+      - ./configuration.yaml:/app/configuration.yaml
+      - ./.env:/app/.env
+    environment:
+      - RUST_LOG=debug
+      - RUST_BACKTRACE=1
+    env_file:
+      - ./.env
+    depends_on:
+      stackerdb:
+        condition: service_healthy
+    entrypoint: /app/console mq listen
 
 #  stackerdb:
 #    container_name: stackerdb
diff --git a/docker/dev/docker-compose.yml b/docker/dev/docker-compose.yml
index 6bf8eb55..3577b145 100644
--- a/docker/dev/docker-compose.yml
+++ b/docker/dev/docker-compose.yml
@@ -37,6 +37,26 @@ services:
       - backend
 
 
+  stacker_queue:
+    image: trydirect/stacker:0.0.7
+    container_name: stacker_queue
+    restart: always
+    volumes:
+      - ./configuration.yaml:/app/configuration.yaml
+      - ./.env:/app/.env
+    environment:
+      - RUST_LOG=debug
+      - RUST_BACKTRACE=1
+    env_file:
+      - ./.env
+    depends_on:
+      stackerdb:
+        condition: service_healthy
+    entrypoint: /app/console mq listen
+    networks:
+      - backend
+
+
   stackerdb:
     container_name: stackerdb
     healthcheck:
diff --git a/src/console/commands/mq/listener.rs b/src/console/commands/mq/listener.rs
index cfe329a3..501d1991 100644
--- a/src/console/commands/mq/listener.rs
+++ b/src/console/commands/mq/listener.rs
@@ -1,19 +1,31 @@
 use crate::configuration::get_configuration;
 use actix_web::rt;
 use actix_web::web;
+use chrono::Utc;
 use lapin::{Channel, Queue};
 use lapin::options::{BasicAckOptions, BasicConsumeOptions};
 use lapin::types::FieldTable;
 use sqlx::PgPool;
 use db::deployment;
-use crate::{db, helpers};
-use crate::helpers::mq_manager;
+use crate::{db, forms, helpers};
+use crate::helpers::{JsonResponse, mq_manager};
 use crate::helpers::mq_manager::MqManager;
 use futures_lite::stream::StreamExt;
+use serde_derive::{Deserialize, Serialize};
+use crate::forms::project::ProjectForm;
 
 pub struct ListenCommand {
 }
 
+#[derive(Serialize, Deserialize, Debug)]
+struct ProgressMessage {
+    alert: i32,
+    id: String,
+    message: String,
+    status: String,
+    progress: String
+}
+
 impl ListenCommand {
     pub fn new() -> Self {
         Self {}
@@ -31,18 +43,20 @@ impl crate::console::commands::CallableTrait for ListenCommand {
 
             let db_pool = web::Data::new(db_pool);
 
+            println!("Declare exchange");
             let mq_manager = MqManager::try_new(settings.amqp.connection_string())?;
             let consumer_channel= mq_manager
                 .consume(
                     "install_progress",
-                    "install_progress_*******"
+                    "install.progress.#"
                 )
                 .await?;
 
 
+            println!("Declare queue");
             let mut consumer = consumer_channel
                 .basic_consume(
-                    "install_progress",
+                    "#",
                     "console_listener",
                     BasicConsumeOptions::default(),
                     FieldTable::default(),
@@ -50,38 +64,46 @@ impl crate::console::commands::CallableTrait for ListenCommand {
                 .await
                 .expect("Basic consume");
 
-                // .map_err(|err| format!("Error {:?}", err));
+            println!("Waiting for messages ..");
+            while let Some(delivery) = consumer.next().await {
+                // println!("checking messages delivery {:?}", delivery);
+                let delivery = delivery.expect("error in consumer");
+                let s:String = match String::from_utf8(delivery.data.to_owned()) {
+                    //delivery.data is of type Vec<u8>
+                    Ok(v) => v,
+                    Err(e) => panic!("Invalid UTF-8 sequence: {}", e),
+                };
 
-            tracing::info!("will consume");
-            // if let Ok(consumer) = consumer {
-                while let Some(delivery) = consumer.next().await {
-                    let delivery = delivery.expect("error in consumer");
-                    delivery.ack(BasicAckOptions::default()).await.expect("ack");
-                }
-            // }
+                match serde_json::from_str::<ProgressMessage>(&s) {
+                    Ok(msg) => {
+                        println!("message {:?}", msg);
+                        // println!("id {:?}", msg.id);
+                        // println!("status {:?}", msg.status);
+                        delivery.ack(BasicAckOptions::default()).await.expect("ack");
 
-            // while let Some(delivery) = consumer.next().await {
-            //     tracing::debug!(message=?delivery, "received message");
-            //     if let Ok(delivery) = delivery {
-            //         delivery
-            //             .ack(BasicAckOptions::default())
-            //             .await
-            //             .expect("basic_ack");
-            //     }
-            // }
+                        if msg.status == "complete" {
+                            let id = msg.id
+                                .parse::<i32>()
+                                .map_err(|err| "Could not parse deployment id".to_string() )?;
 
+                            match crate::db::deployment::fetch(
+                                db_pool.get_ref(), id
+                            )
+                            .await? {
 
-            // on_complete()
-            // let deployment = crate::models::deployment::Deployment {
-            //     id: 0,
-            //     project_id: 0,
-            //     deleted: false,
-            //     status: "".to_string(),
-            //     body: Default::default(),
-            //     created_at: Default::default(),
-            //     updated_at: Default::default(),
-            // };
-            // deployment::update(db_pool.get_ref(), deployment).await?;
+                                Some(mut row) => {
+                                    row.status = msg.status;
+                                    row.updated_at = Utc::now();
+                                    deployment::update(db_pool.get_ref(), row).await?;
+                                    println!("deployment {} completed successfully", id);
+                                }
+                                None => println!("Deployment record not found in db")
+                            }
+                        }
+                    }
+                    Err(err) => { tracing::debug!("Invalid message format")}
+                }
+            }
 
             Ok(())
         })
diff --git a/src/console/main.rs b/src/console/main.rs
index 752e10ae..f6bc804e 100644
--- a/src/console/main.rs
+++ b/src/console/main.rs
@@ -52,7 +52,6 @@ fn get_command(cli: Cli) -> Result<Box<dyn stacker::console::commands::CallableT
             AppMqCommands::Listen {} => Ok(Box::new(
                 stacker::console::commands::mq::ListenCommand::new(),
             )),
-        },
-        _ => Err("command does not match".to_string()),
+        }
     }
 }
diff --git a/src/db/deployment.rs b/src/db/deployment.rs
index 11e7a2e3..7f78f0c2 100644
--- a/src/db/deployment.rs
+++ b/src/db/deployment.rs
@@ -2,6 +2,32 @@ use crate::models;
 use sqlx::PgPool;
 use tracing::Instrument;
 
+
+pub async fn fetch(pool: &PgPool, id: i32) -> Result<Option<models::Deployment>, String> {
+    tracing::info!("Fetch deployment {}", id);
+    sqlx::query_as!(
+        models::Deployment,
+        r#"
+        SELECT
+            *
+        FROM deployment
+        WHERE id=$1
+        LIMIT 1
+        "#,
+        id
+    )
+        .fetch_one(pool)
+        .await
+        .map(|deployment| Some(deployment))
+        .or_else(|err| match err {
+            sqlx::Error::RowNotFound => Ok(None),
+            e => {
+                tracing::error!("Failed to fetch deployment, error: {:?}", e);
+                Err("Could not fetch data".to_string())
+            }
+        })
+}
+
 pub async fn insert(pool: &PgPool, mut deployment: models::Deployment) -> Result<models::Deployment, String> {
     let query_span = tracing::info_span!("Saving new deployment into the database");
     sqlx::query!(
diff --git a/src/forms/cloud.rs b/src/forms/cloud.rs
index 138b2989..472bdb70 100644
--- a/src/forms/cloud.rs
+++ b/src/forms/cloud.rs
@@ -15,14 +15,14 @@ pub struct Cloud {
     pub save_token: Option<bool>,
 }
 
-impl Into<models::Cloud> for Cloud {
+impl Into<models::Cloud> for &Cloud {
     fn into(self) -> models::Cloud {
         let mut cloud = models::Cloud::default();
-        cloud.provider = self.provider;
-        cloud.cloud_token = self.cloud_token;
-        cloud.cloud_key = self.cloud_key;
-        cloud.cloud_secret = self.cloud_secret;
-        cloud.save_token = self.save_token;
+        cloud.provider = self.provider.clone();
+        cloud.cloud_token = self.cloud_token.clone();
+        cloud.cloud_key = self.cloud_key.clone();
+        cloud.cloud_secret = self.cloud_secret.clone();
+        cloud.save_token = self.save_token.clone();
 
         cloud
     }
diff --git a/src/forms/mod.rs b/src/forms/mod.rs
index f5fcc9cb..06471817 100644
--- a/src/forms/mod.rs
+++ b/src/forms/mod.rs
@@ -7,3 +7,4 @@ pub(crate) mod server;
 pub use rating::*;
 pub use cloud::*;
 pub use server::*;
+pub use user::UserForm;
diff --git a/src/forms/server.rs b/src/forms/server.rs
index 1e55a895..cba997f9 100644
--- a/src/forms/server.rs
+++ b/src/forms/server.rs
@@ -14,14 +14,14 @@ pub struct Server {
     pub disk_type: Option<String>,
 }
 
-impl Into<models::Server> for Server {
+impl Into<models::Server> for &Server {
     fn into(self) -> models::Server {
         let mut server = models::Server::default();
-        server.disk_type = self.disk_type;
-        server.region = self.region;
-        server.server = self.server;
-        server.zone = self.zone;
-        server.os = self.os;
+        server.disk_type = self.disk_type.clone();
+        server.region = self.region.clone();
+        server.server = self.server.clone();
+        server.zone = self.zone.clone();
+        server.os = self.os.clone();
         server.created_at = Utc::now();
         server.updated_at = Utc::now();
 
diff --git a/src/forms/user.rs b/src/forms/user.rs
index 8ff0b4ce..0fd00246 100644
--- a/src/forms/user.rs
+++ b/src/forms/user.rs
@@ -10,6 +10,8 @@ pub struct UserForm {
     pub user: User,
 }
 
+//todo deref for UserForm. userForm.id, userForm.first_name
+
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 #[serde(rename_all = "camelCase")]
 pub struct User {
@@ -139,4 +141,4 @@ impl TryInto<UserModel> for UserForm {
         })
     }
 
-}
\ No newline at end of file
+}
diff --git a/src/helpers/mq_manager.rs b/src/helpers/mq_manager.rs
index c2658251..f30d2385 100644
--- a/src/helpers/mq_manager.rs
+++ b/src/helpers/mq_manager.rs
@@ -4,6 +4,7 @@ use lapin::{options::*, publisher_confirm::{Confirmation, PublisherConfirm}, Bas
 use lapin::types::AMQPType::ShortString;
 use lapin::types::{AMQPValue, FieldTable};
 use serde::ser::Serialize;
+use serde_valid::validation::error::Format::Default;
 
 #[derive(Debug)]
 pub struct MqManager {
@@ -127,10 +128,22 @@ impl MqManager {
             .await
             .expect("Exchange declare failed");
 
+        let mut args = FieldTable::default();
+        args.insert("x-expires".into(), AMQPValue::LongUInt(180000));
+
         let queue = channel.queue_declare(
-            routing_key,
-            QueueDeclareOptions::default(),
-            Default::default(),
+            // "install_progress_all",
+            "#",
+            // "install_progress_hy181TZa4DaabUZWklsrxw",
+            QueueDeclareOptions {
+                passive: false,
+                durable: false,
+                exclusive: false,
+                auto_delete: true,
+                nowait: false,
+            },
+            // FieldTable::default(),
+            args,
         )
         .await
         .expect("Queue declare failed");
diff --git a/src/routes/cloud/add.rs b/src/routes/cloud/add.rs
index cb572096..6178ff1f 100644
--- a/src/routes/cloud/add.rs
+++ b/src/routes/cloud/add.rs
@@ -1,3 +1,4 @@
+use std::ops::Deref;
 use crate::forms;
 use crate::helpers::JsonResponse;
 use crate::models;
@@ -29,7 +30,7 @@ pub async fn add(
         return Err(JsonResponse::<models::Project>::build().form_error(errors));
     }
 
-    let mut cloud: models::Cloud = form.into_inner().into();
+    let mut cloud: models::Cloud = form.deref().into();
     cloud.user_id = user.id.clone();
 
     db::cloud::insert(pg_pool.get_ref(), cloud)
diff --git a/src/routes/cloud/update.rs b/src/routes/cloud/update.rs
index 3e2e564d..b48fd8bc 100644
--- a/src/routes/cloud/update.rs
+++ b/src/routes/cloud/update.rs
@@ -7,6 +7,7 @@ use serde_valid::Validate;
 use sqlx::PgPool;
 use std::sync::Arc;
 use tracing::Instrument;
+use std::ops::Deref;
 
 #[tracing::instrument(name = "Update cloud.")]
 #[put("/{id}")]
@@ -33,7 +34,7 @@ pub async fn item(
         return Err(JsonResponse::<models::Cloud>::build().form_error(errors.to_string()));
     }
 
-    let mut cloud:models::Cloud = form.into_inner().into();
+    let mut cloud:models::Cloud = form.deref().into();
     cloud.id = cloud_row.id;
     cloud.user_id = user.id.clone();
 
diff --git a/src/routes/project/deploy.rs b/src/routes/project/deploy.rs
index a1261ab6..9844035f 100644
--- a/src/routes/project/deploy.rs
+++ b/src/routes/project/deploy.rs
@@ -43,33 +43,30 @@ pub async fn item(
         })?;
 
     // Build compose
-    let id = project.id.clone();
+    let id = project.id;
     let dc = DcBuilder::new(project);
     let fc = dc.build().map_err(|err| {
         JsonResponse::<models::Project>::build().internal_server_error(err)
     })?;
 
-
     // Save cloud credentials if requested
-    let mut cloud_creds: models::Cloud = form.cloud.clone().into();
-    cloud_creds.project_id = Some(id.clone());
+    let mut cloud_creds: models::Cloud = (&form.cloud).into();
+    cloud_creds.project_id = Some(id);
     cloud_creds.user_id = user.id.clone();
 
-    if let Some(save_token) = cloud_creds.save_token {
-        if save_token {
-            db::cloud::insert(pg_pool.get_ref(), cloud_creds.clone())
-                .await
-                .map(|cloud| cloud)
-                .map_err(|_| {
-                    JsonResponse::<models::Cloud>::build().internal_server_error("Internal Server Error")
-                })?;
-        }
+    if Some(true) == cloud_creds.save_token {
+        db::cloud::insert(pg_pool.get_ref(), cloud_creds.clone())
+            .await
+            .map(|cloud| cloud)
+            .map_err(|_| {
+                JsonResponse::<models::Cloud>::build().internal_server_error("Internal Server Error")
+            })?;
     }
 
     // Save server type and region
-    let mut server: models::Server = form.server.clone().into();
+    let mut server: models::Server = (&form.server).into();
     server.user_id = user.id.clone();
-    server.project_id = id.clone();
+    server.project_id = id;
     let server = db::server::insert(pg_pool.get_ref(), server)
         .await
         .map(|server| server)
@@ -89,10 +86,9 @@ pub async fn item(
     payload.docker_compose = Some(compress(fc.as_str()));
 
     // Store deployment attempts into deployment table in db
-    let project_id = dc.project.id.clone();
     let json_request = dc.project.body.clone();
     let deployment = models::Deployment::new(
-        project_id,
+        dc.project.id,
         String::from("pending"),
         json_request
     );
@@ -189,10 +185,9 @@ pub async fn saved_item(
     payload.docker_compose = Some(compress(fc.as_str()));
 
 
-    let project_id = dc.project.id.clone();
     let json_request = dc.project.body.clone();
     let deployment = models::Deployment::new(
-        project_id,
+        dc.project.id,
         String::from("pending"),
         json_request
     );
diff --git a/src/routes/server/update.rs b/src/routes/server/update.rs
index 62208698..02c4b0eb 100644
--- a/src/routes/server/update.rs
+++ b/src/routes/server/update.rs
@@ -7,6 +7,7 @@ use serde_valid::Validate;
 use sqlx::PgPool;
 use std::sync::Arc;
 use tracing::Instrument;
+use std::ops::Deref;
 
 #[tracing::instrument(name = "Update server.")]
 #[put("/{id}")]
@@ -33,7 +34,7 @@ pub async fn item(
         return Err(JsonResponse::<models::Server>::build().form_error(errors.to_string()));
     }
 
-    let mut server:models::Server = form.into_inner().into();
+    let mut server:models::Server = form.deref().into();
     server.id = server_row.id;
     server.project_id = server_row.project_id;
     server.user_id = user.id.clone();
diff --git a/src/startup.rs b/src/startup.rs
index 2c7c4baa..a95acfb8 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -7,7 +7,6 @@ use actix_web::{
     App, HttpServer,
 };
 use crate::middleware;
-use actix_web_httpauth::middleware::HttpAuthentication;
 use sqlx::{Pool, Postgres};
 use std::net::TcpListener;
 use tracing_actix_web::TracingLogger;
diff --git a/tests/middleware_trydirect.rs b/tests/middleware_trydirect.rs
index 05906181..49377813 100644
--- a/tests/middleware_trydirect.rs
+++ b/tests/middleware_trydirect.rs
@@ -22,8 +22,4 @@ async fn middleware_trydirect_works() {
 
     assert!(response.status().is_success());
     assert_eq!(Some(0), response.content_length());
-
-
-    //todo header stacker-id not found
-    //
 }

From dc6c54fffd04b58e2a6ec4ec3b7c8464be0f6dd2 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Wed, 20 Mar 2024 15:42:10 +0200
Subject: [PATCH 221/284] fix warnings

---
 docker-compose.yml           | 32 ++++++++++++++++----------------
 src/db/cloud.rs              |  2 +-
 src/db/project.rs            |  4 ++--
 src/db/server.rs             |  2 +-
 src/forms/project/form.rs    |  2 +-
 src/helpers/dockerhub.rs     |  6 +++---
 src/routes/project/deploy.rs |  4 ++--
 src/routes/server/update.rs  |  2 +-
 src/startup.rs               |  1 +
 9 files changed, 28 insertions(+), 27 deletions(-)

diff --git a/docker-compose.yml b/docker-compose.yml
index f87b3e27..eeb10eb2 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -29,22 +29,22 @@ services:
 #      stackerdb:
 #        condition: service_healthy
 
-  stacker_queue:
-    image: trydirect/stacker:0.0.7
-    container_name: stacker_queue
-    restart: always
-    volumes:
-      - ./configuration.yaml:/app/configuration.yaml
-      - ./.env:/app/.env
-    environment:
-      - RUST_LOG=debug
-      - RUST_BACKTRACE=1
-    env_file:
-      - ./.env
-    depends_on:
-      stackerdb:
-        condition: service_healthy
-    entrypoint: /app/console mq listen
+#  stacker_queue:
+#    image: trydirect/stacker:0.0.7
+#    container_name: stacker_queue
+#    restart: always
+#    volumes:
+#      - ./configuration.yaml:/app/configuration.yaml
+#      - ./.env:/app/.env
+#    environment:
+#      - RUST_LOG=debug
+#      - RUST_BACKTRACE=1
+#    env_file:
+#      - ./.env
+#    depends_on:
+#      stackerdb:
+#        condition: service_healthy
+#    entrypoint: /app/console mq listen
 
 #  stackerdb:
 #    container_name: stackerdb
diff --git a/src/db/cloud.rs b/src/db/cloud.rs
index 07f52851..a5e71c2c 100644
--- a/src/db/cloud.rs
+++ b/src/db/cloud.rs
@@ -151,7 +151,7 @@ pub async fn delete(pool: &PgPool, id: i32) -> Result<bool, String> {
             });
             Ok(true)
         }
-        Err(err) => {
+        Err(_err) => {
             let _ = tx.rollback().await.map_err(|err| println!("{:?}", err));
             Ok(false)
         }
diff --git a/src/db/project.rs b/src/db/project.rs
index d1f469f8..b7978e6a 100644
--- a/src/db/project.rs
+++ b/src/db/project.rs
@@ -167,14 +167,14 @@ pub async fn delete(pool: &PgPool, id: i32) -> Result<bool, String> {
         })
     {
         Ok(_) => {
-            tx.commit().await.map_err(|err| {
+            let _ = tx.commit().await.map_err(|err| {
                 tracing::error!("Failed to commit transaction: {:?}", err);
                 false
             });
             Ok(true)
         }
         Err(err) => {
-            tx.rollback().await.map_err(|err| println!("{:?}", err));
+            let _ = tx.rollback().await.map_err(|err| println!("{:?}", err));
             Ok(false)
         }
         // todo, when empty commit()
diff --git a/src/db/server.rs b/src/db/server.rs
index 86f147b2..75513a99 100644
--- a/src/db/server.rs
+++ b/src/db/server.rs
@@ -182,7 +182,7 @@ pub async fn delete(pool: &PgPool, id: i32) -> Result<bool, String> {
             });
             Ok(true)
         }
-        Err(err) => {
+        Err(_err) => {
             let _ = tx.rollback().await.map_err(|err| println!("{:?}", err));
             Ok(false)
         }
diff --git a/src/forms/project/form.rs b/src/forms/project/form.rs
index d2498cf4..c122ffce 100644
--- a/src/forms/project/form.rs
+++ b/src/forms/project/form.rs
@@ -63,7 +63,7 @@ pub(crate) async fn body_into_form(body: Bytes) -> actix_web::Result<forms::proj
             let msg = format!("{}:{:?}", err.path().to_string(), err);
             JsonResponse::<forms::project::ProjectForm>::build().bad_request(msg)
         })
-        .and_then(|mut form: forms::project::ProjectForm| {
+        .and_then(|form: forms::project::ProjectForm| {
             if !form.validate().is_ok() {
                 let errors = form.validate().unwrap_err().to_string();
                 let err_msg = format!("Invalid data received {:?}", &errors);
diff --git a/src/helpers/dockerhub.rs b/src/helpers/dockerhub.rs
index d5e4ad67..d7771c75 100644
--- a/src/helpers/dockerhub.rs
+++ b/src/helpers/dockerhub.rs
@@ -111,7 +111,7 @@ impl<'a> DockerHub<'a> {
         let client = reqwest::Client::new()
             .get(url)
             .header("Accept", "application/json");
-        let mut client = self.set_token(client).await?;
+        let client = self.set_token(client).await?;
         client
             .send()
             .await
@@ -146,7 +146,7 @@ impl<'a> DockerHub<'a> {
         let client = reqwest::Client::new()
             .get(url)
             .header("Accept", "application/json");
-        let mut client = self.set_token(client).await?;
+        let client = self.set_token(client).await?;
         client
             .send()
             .await
@@ -186,7 +186,7 @@ impl<'a> DockerHub<'a> {
         }
     }
 
-    pub async fn set_token(&self, mut client: RequestBuilder) -> Result<RequestBuilder, String> {
+    pub async fn set_token(&self, client: RequestBuilder) -> Result<RequestBuilder, String> {
         if self.creds.password.is_empty() {
             tracing::debug!("Password is empty. Image should be public");
             return Ok(client);
diff --git a/src/routes/project/deploy.rs b/src/routes/project/deploy.rs
index 9844035f..f3d242a7 100644
--- a/src/routes/project/deploy.rs
+++ b/src/routes/project/deploy.rs
@@ -156,7 +156,7 @@ pub async fn saved_item(
                 }
             }
         }
-        Err(e) => {
+        Err(_e) => {
             return Err(JsonResponse::<models::Project>::build().not_found("No cloud configured"));
         }
     };
@@ -169,7 +169,7 @@ pub async fn saved_item(
             // }
             server.into_iter().nth(0).unwrap() // @todo refactoring is required
         }
-        Err(err) => {
+        Err(_e) => {
             return Err(JsonResponse::<models::Project>::build().not_found("No servers configured"));
         }
     };
diff --git a/src/routes/server/update.rs b/src/routes/server/update.rs
index 02c4b0eb..4f83d1ea 100644
--- a/src/routes/server/update.rs
+++ b/src/routes/server/update.rs
@@ -19,7 +19,7 @@ pub async fn item(
 ) -> Result<impl Responder> {
 
     let id = path.0;
-    let mut server_row = db::server::fetch(pg_pool.get_ref(), id)
+    let server_row = db::server::fetch(pg_pool.get_ref(), id)
         .await
         .map_err(|err| JsonResponse::<models::Server>::build().internal_server_error(err))
         .and_then(|server| match server {
diff --git a/src/startup.rs b/src/startup.rs
index a95acfb8..2c7c4baa 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -7,6 +7,7 @@ use actix_web::{
     App, HttpServer,
 };
 use crate::middleware;
+use actix_web_httpauth::middleware::HttpAuthentication;
 use sqlx::{Pool, Postgres};
 use std::net::TcpListener;
 use tracing_actix_web::TracingLogger;

From 3e367ee3dfdc4259039c3ec8a6695141ed784eec Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Wed, 20 Mar 2024 15:51:32 +0200
Subject: [PATCH 222/284] fix restart policy for service

---
 src/forms/project/app.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/forms/project/app.rs b/src/forms/project/app.rs
index 64598e61..56a5da99 100644
--- a/src/forms/project/app.rs
+++ b/src/forms/project/app.rs
@@ -143,7 +143,7 @@ impl App {
 
 
         service.ports = dctypes::Ports::Long(ports);
-        service.restart = Some("always".to_owned());
+        service.restart = Some(self.restart.clone());
         service.volumes = volumes;
         service.environment = dctypes::Environment::KvPair(envs);
 

From 858ab0e890f8073e4856135f7314d9a3d1c6d4d8 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Thu, 21 Mar 2024 11:52:25 +0200
Subject: [PATCH 223/284] import fixes

---
 src/console/commands/mq/listener.rs   | 25 ++++++++++++++-----------
 src/db/project.rs                     |  2 +-
 src/forms/cloud.rs                    |  1 -
 src/forms/project/compose_networks.rs |  1 -
 src/forms/project/form.rs             |  1 -
 src/forms/project/network.rs          |  2 --
 src/forms/project/payload.rs          |  1 -
 src/forms/project/port.rs             |  2 +-
 src/forms/user.rs                     |  1 -
 src/helpers/dockerhub.rs              | 10 ++++++----
 src/helpers/mq_manager.rs             |  5 +----
 src/helpers/project/builder.rs        |  7 +++----
 src/middleware/access_manager.rs      |  2 +-
 src/middleware/trydirect.rs           |  1 -
 src/models/deployment.rs              |  7 ++++++-
 src/models/project.rs                 |  7 ++++++-
 src/routes/client/add.rs              |  1 -
 src/routes/client/disable.rs          |  1 -
 src/routes/client/enable.rs           |  1 -
 src/routes/client/update.rs           |  1 -
 src/routes/cloud/add.rs               |  1 -
 src/routes/cloud/delete.rs            |  4 +---
 src/routes/cloud/get.rs               |  1 -
 src/routes/cloud/mod.rs               |  8 ++++----
 src/routes/cloud/update.rs            |  3 +--
 src/routes/project/compose.rs         |  2 +-
 src/routes/project/delete.rs          |  4 +---
 src/routes/project/get.rs             |  9 ++++-----
 src/routes/project/mod.rs             |  8 ++++----
 src/routes/project/update.rs          |  1 -
 src/routes/rating/add.rs              |  1 -
 src/routes/rating/get.rs              |  1 -
 src/routes/server/delete.rs           |  3 +--
 src/routes/server/get.rs              |  2 +-
 src/routes/server/mod.rs              |  8 ++++----
 src/routes/server/update.rs           |  3 +--
 36 files changed, 62 insertions(+), 76 deletions(-)

diff --git a/src/console/commands/mq/listener.rs b/src/console/commands/mq/listener.rs
index 501d1991..a9e6c045 100644
--- a/src/console/commands/mq/listener.rs
+++ b/src/console/commands/mq/listener.rs
@@ -2,25 +2,24 @@ use crate::configuration::get_configuration;
 use actix_web::rt;
 use actix_web::web;
 use chrono::Utc;
-use lapin::{Channel, Queue};
 use lapin::options::{BasicAckOptions, BasicConsumeOptions};
 use lapin::types::FieldTable;
 use sqlx::PgPool;
 use db::deployment;
-use crate::{db, forms, helpers};
-use crate::helpers::{JsonResponse, mq_manager};
+use crate::db;
 use crate::helpers::mq_manager::MqManager;
 use futures_lite::stream::StreamExt;
 use serde_derive::{Deserialize, Serialize};
-use crate::forms::project::ProjectForm;
+// use crate::forms::project::ProjectForm;
 
 pub struct ListenCommand {
 }
 
 #[derive(Serialize, Deserialize, Debug)]
 struct ProgressMessage {
-    alert: i32,
     id: String,
+    deploy_id: Option<String>,
+    alert: i32,
     message: String,
     status: String,
     progress: String
@@ -74,17 +73,19 @@ impl crate::console::commands::CallableTrait for ListenCommand {
                     Err(e) => panic!("Invalid UTF-8 sequence: {}", e),
                 };
 
+                println!("incoming data {:?}", s);
+                let statuses = vec!["complete", "paused", "failed"];
                 match serde_json::from_str::<ProgressMessage>(&s) {
                     Ok(msg) => {
-                        println!("message {:?}", msg);
+                        println!("message {:?}", s);
                         // println!("id {:?}", msg.id);
                         // println!("status {:?}", msg.status);
-                        delivery.ack(BasicAckOptions::default()).await.expect("ack");
 
-                        if msg.status == "complete" {
-                            let id = msg.id
+                        if statuses.contains(&(msg.status.as_str())) {
+                            println!("Process on complete status");
+                            let id = msg.deploy_id.unwrap()
                                 .parse::<i32>()
-                                .map_err(|err| "Could not parse deployment id".to_string() )?;
+                                .map_err(|_err| "Could not parse deployment id".to_string() )?;
 
                             match crate::db::deployment::fetch(
                                 db_pool.get_ref(), id
@@ -101,8 +102,10 @@ impl crate::console::commands::CallableTrait for ListenCommand {
                             }
                         }
                     }
-                    Err(err) => { tracing::debug!("Invalid message format")}
+                    Err(_err) => { tracing::debug!("Invalid message format {:?}", _err)}
                 }
+
+                delivery.ack(BasicAckOptions::default()).await.expect("ack");
             }
 
             Ok(())
diff --git a/src/db/project.rs b/src/db/project.rs
index b7978e6a..0e8e24c5 100644
--- a/src/db/project.rs
+++ b/src/db/project.rs
@@ -173,7 +173,7 @@ pub async fn delete(pool: &PgPool, id: i32) -> Result<bool, String> {
             });
             Ok(true)
         }
-        Err(err) => {
+        Err(_err) => {
             let _ = tx.rollback().await.map_err(|err| println!("{:?}", err));
             Ok(false)
         }
diff --git a/src/forms/cloud.rs b/src/forms/cloud.rs
index 472bdb70..ab040c37 100644
--- a/src/forms/cloud.rs
+++ b/src/forms/cloud.rs
@@ -1,7 +1,6 @@
 use crate::models;
 use serde::{Deserialize, Serialize};
 use serde_valid::Validate;
-use chrono::Utc;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Cloud {
diff --git a/src/forms/project/compose_networks.rs b/src/forms/project/compose_networks.rs
index bb45a365..b38eb8fa 100644
--- a/src/forms/project/compose_networks.rs
+++ b/src/forms/project/compose_networks.rs
@@ -1,7 +1,6 @@
 use serde::{Deserialize, Serialize};
 use docker_compose_types as dctypes;
 use indexmap::IndexMap;
-use crate::forms::project;
 use crate::forms::project::network::Network;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
diff --git a/src/forms/project/form.rs b/src/forms/project/form.rs
index c122ffce..3924c6c7 100644
--- a/src/forms/project/form.rs
+++ b/src/forms/project/form.rs
@@ -1,5 +1,4 @@
 use serde::{Deserialize, Serialize};
-use serde_json::Value;
 use serde_valid::Validate;
 use actix_web::Error;
 use actix_web::web::Bytes;
diff --git a/src/forms/project/network.rs b/src/forms/project/network.rs
index 9b848ff9..2e0e1835 100644
--- a/src/forms/project/network.rs
+++ b/src/forms/project/network.rs
@@ -1,8 +1,6 @@
 use serde::{Deserialize, Serialize};
 use serde_valid::Validate;
-use crate::forms::project;
 use docker_compose_types as dctypes;
-use indexmap::IndexMap;
 use crate::forms::project::NetworkDriver;
 
 
diff --git a/src/forms/project/payload.rs b/src/forms/project/payload.rs
index 7108a1f5..bc8e8fba 100644
--- a/src/forms/project/payload.rs
+++ b/src/forms/project/payload.rs
@@ -1,7 +1,6 @@
 use std::convert::TryFrom;
 use crate::models;
 use crate::forms;
-use serde_json::Value;
 use serde::{Deserialize, Serialize};
 use serde_valid::Validate;
 
diff --git a/src/forms/project/port.rs b/src/forms/project/port.rs
index 4da0c497..06c30208 100644
--- a/src/forms/project/port.rs
+++ b/src/forms/project/port.rs
@@ -53,7 +53,7 @@ impl TryInto<dctypes::Port> for &Port {
         let cp = self.container_port
             .clone()
             .parse::<u16>()
-            .map_err(|err| "Could not parse container port".to_string() )?;
+            .map_err(|_err| "Could not parse container port".to_string() )?;
 
         let hp = match self.host_port.clone() {
             Some(hp) => {
diff --git a/src/forms/user.rs b/src/forms/user.rs
index 0fd00246..5a7fa48f 100644
--- a/src/forms/user.rs
+++ b/src/forms/user.rs
@@ -1,7 +1,6 @@
 use serde_derive::{Serialize, Deserialize};
 use serde_json::Value;
 use serde_valid::{Validate};
-use tracing_subscriber::fmt::format;
 use crate::models::user::User as UserModel;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
diff --git a/src/helpers/dockerhub.rs b/src/helpers/dockerhub.rs
index d7771c75..fa41df77 100644
--- a/src/helpers/dockerhub.rs
+++ b/src/helpers/dockerhub.rs
@@ -116,12 +116,12 @@ impl<'a> DockerHub<'a> {
             .send()
             .await
             .map_err(|err| {
-                tracing::debug!("Error response {:?}", err);
+                tracing::debug!("🟥Error response {:?}", err);
                 format!("{}", err)
             })?
             .json::<RepoResults>()
             .await
-            .map_err(|err| format!("Error on getting results:: {}", err))
+            .map_err(|err| format!("🟥Error on getting results:: {}", err))
             .map(|repositories| {
                 tracing::debug!("Get public image repositories response {:?}", repositories);
                 if repositories.count.unwrap_or(0) > 0 {
@@ -130,6 +130,7 @@ impl<'a> DockerHub<'a> {
                         .results
                         .into_iter()
                         .any(|repo| repo.status == 1);
+                    tracing::debug!("✅ Image is active");
                     active
                 } else {
                     false
@@ -150,11 +151,11 @@ impl<'a> DockerHub<'a> {
         client
             .send()
             .await
-            .map_err(|err| format!("{}", err))?
+            .map_err(|err| format!("🟥{}", err))?
             .json::<TagResult>()
             .await
             .map_err(|err| {
-                tracing::debug!("Error response {:?}", err);
+                tracing::debug!("🟥Error response {:?}", err);
                 format!("{}", err)
             })
             .map(|tags| {
@@ -165,6 +166,7 @@ impl<'a> DockerHub<'a> {
                         .results
                         .into_iter()
                         .any(|tag| tag.tag_status.contains("active"));
+                    tracing::debug!("✅ Image is active");
                     active
                 } else {
                     false
diff --git a/src/helpers/mq_manager.rs b/src/helpers/mq_manager.rs
index f30d2385..f205f1a5 100644
--- a/src/helpers/mq_manager.rs
+++ b/src/helpers/mq_manager.rs
@@ -1,10 +1,7 @@
-use actix_web::web;
 use deadpool_lapin::{Config, CreatePoolError, Object, Pool, Runtime};
-use lapin::{options::*, publisher_confirm::{Confirmation, PublisherConfirm}, BasicProperties, Channel, ExchangeKind, Queue};
-use lapin::types::AMQPType::ShortString;
+use lapin::{options::*, publisher_confirm::{Confirmation, PublisherConfirm}, BasicProperties, Channel, ExchangeKind};
 use lapin::types::{AMQPValue, FieldTable};
 use serde::ser::Serialize;
-use serde_valid::validation::error::Format::Default;
 
 #[derive(Debug)]
 pub struct MqManager {
diff --git a/src/helpers/project/builder.rs b/src/helpers/project/builder.rs
index 7112fe30..9c2a33a5 100644
--- a/src/helpers/project/builder.rs
+++ b/src/helpers/project/builder.rs
@@ -2,14 +2,13 @@ use crate::forms;
 use docker_compose_types as dctypes;
 use crate::models;
 use serde_yaml;
-use crate::helpers::project::*;
-use tracing::Value;
+// use crate::helpers::project::*;
 
 
 /// A builder for constructing docker compose.
 #[derive(Clone, Debug)]
 pub struct DcBuilder {
-    config: Config,
+    // config: Config,
     pub(crate) project: models::Project,
 }
 
@@ -17,7 +16,7 @@ pub struct DcBuilder {
 impl DcBuilder {
     pub fn new(project: models::Project) -> Self {
         DcBuilder {
-            config: Config::default(),
+            // config: Config::default(),
             project,
         }
     }
diff --git a/src/middleware/access_manager.rs b/src/middleware/access_manager.rs
index 47601146..f251e9d0 100644
--- a/src/middleware/access_manager.rs
+++ b/src/middleware/access_manager.rs
@@ -17,7 +17,7 @@ pub async fn try_new(db_connection_address: String) -> Result<CasbinService, Err
         .await
         .map_err(|err| Error::new(ErrorKind::Other, format!("{err:?}")))?;
 
-    let mut casbin_service = CasbinService::new(m, a)
+    let casbin_service = CasbinService::new(m, a)
         .await
         .map_err(|err| Error::new(ErrorKind::Other, format!("{err:?}")))?;
 
diff --git a/src/middleware/trydirect.rs b/src/middleware/trydirect.rs
index 1c61049f..0a6369be 100644
--- a/src/middleware/trydirect.rs
+++ b/src/middleware/trydirect.rs
@@ -1,7 +1,6 @@
 use crate::{models, configuration::Settings, forms::user::UserForm, helpers::JsonResponse};
 use actix_web::{web, dev::ServiceRequest, Error, HttpMessage};
 use actix_web_httpauth::extractors::bearer::BearerAuth;
-use futures::future::{FutureExt};
 use reqwest::header::{ACCEPT, CONTENT_TYPE};
 use std::sync::Arc;
 
diff --git a/src/models/deployment.rs b/src/models/deployment.rs
index bfdd72b0..b819ef23 100644
--- a/src/models/deployment.rs
+++ b/src/models/deployment.rs
@@ -31,8 +31,13 @@ impl Deployment {
 impl Default for Deployment {
     fn default() -> Self {
         Deployment {
+            id: 0,
+            project_id: 0,
+            deleted: None,
             status: "pending".to_string(),
-            ..Default::default()
+            body: Default::default(),
+            created_at: Default::default(),
+            updated_at: Default::default(),
         }
     }
 }
diff --git a/src/models/project.rs b/src/models/project.rs
index 77422675..29b260bd 100644
--- a/src/models/project.rs
+++ b/src/models/project.rs
@@ -34,9 +34,14 @@ impl Project {
 impl Default for Project {
     fn default() -> Self {
         Project {
+            id: 0,
+            stack_id: Default::default(),
             user_id: "".to_string(),
             name: "".to_string(),
-            ..Default::default()
+            body: Default::default(),
+            request_json: Default::default(),
+            created_at: Default::default(),
+            updated_at: Default::default(),
         }
     }
 }
diff --git a/src/routes/client/add.rs b/src/routes/client/add.rs
index 0b526d10..bddbb74a 100644
--- a/src/routes/client/add.rs
+++ b/src/routes/client/add.rs
@@ -6,7 +6,6 @@ use crate::models;
 use actix_web::{post, web, Responder, Result};
 use sqlx::PgPool;
 use std::sync::Arc;
-use tracing::Instrument;
 
 #[tracing::instrument(name = "Add client.")]
 #[post("")]
diff --git a/src/routes/client/disable.rs b/src/routes/client/disable.rs
index 70d2a1cb..eb4f43ac 100644
--- a/src/routes/client/disable.rs
+++ b/src/routes/client/disable.rs
@@ -5,7 +5,6 @@ use crate::models;
 use actix_web::{put, web, Responder, Result};
 use sqlx::PgPool;
 use std::sync::Arc;
-use tracing::Instrument;
 
 #[tracing::instrument(name = "Disable client.")]
 #[put("/{id}/disable")]
diff --git a/src/routes/client/enable.rs b/src/routes/client/enable.rs
index c87fc445..200f080f 100644
--- a/src/routes/client/enable.rs
+++ b/src/routes/client/enable.rs
@@ -6,7 +6,6 @@ use crate::models;
 use actix_web::{put, web, Responder, Result};
 use sqlx::PgPool;
 use std::sync::Arc;
-use tracing::Instrument;
 
 #[tracing::instrument(name = "Enable client.")]
 #[put("/{id}/enable")]
diff --git a/src/routes/client/update.rs b/src/routes/client/update.rs
index 5f19de51..243cfa86 100644
--- a/src/routes/client/update.rs
+++ b/src/routes/client/update.rs
@@ -5,7 +5,6 @@ use crate::{configuration::Settings, helpers::JsonResponse};
 use actix_web::{put, web, Responder, Result};
 use sqlx::PgPool;
 use std::sync::Arc;
-use tracing::Instrument;
 
 #[tracing::instrument(name = "Update client.")]
 #[put("/{id}")]
diff --git a/src/routes/cloud/add.rs b/src/routes/cloud/add.rs
index 6178ff1f..ca8e7b80 100644
--- a/src/routes/cloud/add.rs
+++ b/src/routes/cloud/add.rs
@@ -5,7 +5,6 @@ use crate::models;
 use crate::db;
 use actix_web::{post, web, Responder, Result};
 use sqlx::PgPool;
-use tracing::Instrument;
 use std::sync::Arc;
 use serde_valid::Validate;
 
diff --git a/src/routes/cloud/delete.rs b/src/routes/cloud/delete.rs
index b69aec4e..94188c71 100644
--- a/src/routes/cloud/delete.rs
+++ b/src/routes/cloud/delete.rs
@@ -3,8 +3,6 @@ use crate::models;
 use actix_web::{delete, web, Responder, Result};
 use sqlx::PgPool;
 use std::sync::Arc;
-use futures_util::FutureExt;
-use tracing::Instrument;
 use crate::db;
 use crate::models::Cloud;
 
@@ -15,7 +13,7 @@ pub async fn item(
     path: web::Path<(i32,)>,
     pg_pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
-    /// Get cloud apps of logged user only
+    // Get cloud apps of logged user only
     let (id,) = path.into_inner();
 
     let cloud = db::cloud::fetch(pg_pool.get_ref(), id)
diff --git a/src/routes/cloud/get.rs b/src/routes/cloud/get.rs
index 62f2a472..23af867c 100644
--- a/src/routes/cloud/get.rs
+++ b/src/routes/cloud/get.rs
@@ -4,7 +4,6 @@ use crate::helpers::JsonResponse;
 use crate::models;
 use actix_web::{get, web, Responder, Result};
 use sqlx::PgPool;
-use tracing::Instrument;
 
 // workflow
 // add, update, list, get(user_id), ACL,
diff --git a/src/routes/cloud/mod.rs b/src/routes/cloud/mod.rs
index bc45c2a7..e4ea6c1e 100644
--- a/src/routes/cloud/mod.rs
+++ b/src/routes/cloud/mod.rs
@@ -3,7 +3,7 @@ pub mod get;
 pub mod update;
 pub(crate) mod delete;
 
-pub use add::*;
-pub use get::*;
-pub use update::*;
-pub use delete::*;
+// pub use add::*;
+// pub use get::*;
+// pub use update::*;
+// pub use delete::*;
diff --git a/src/routes/cloud/update.rs b/src/routes/cloud/update.rs
index b48fd8bc..bdc0a150 100644
--- a/src/routes/cloud/update.rs
+++ b/src/routes/cloud/update.rs
@@ -2,11 +2,10 @@ use crate::forms;
 use crate::helpers::JsonResponse;
 use crate::models;
 use crate::db;
-use actix_web::{web, web::Data, Responder, Result, post, put};
+use actix_web::{web, web::Data, Responder, Result, put};
 use serde_valid::Validate;
 use sqlx::PgPool;
 use std::sync::Arc;
-use tracing::Instrument;
 use std::ops::Deref;
 
 #[tracing::instrument(name = "Update cloud.")]
diff --git a/src/routes/project/compose.rs b/src/routes/project/compose.rs
index d0ea4323..de52ca03 100644
--- a/src/routes/project/compose.rs
+++ b/src/routes/project/compose.rs
@@ -40,7 +40,7 @@ pub async fn admin(
     path: web::Path<(i32,)>,
     pg_pool: Data<PgPool>,
 ) -> Result<impl Responder> {
-    ///  Admin function for generating compose file for specified user
+    //  Admin function for generating compose file for specified user
     let id = path.0;
     let project = db::project::fetch(pg_pool.get_ref(), id)
         .await
diff --git a/src/routes/project/delete.rs b/src/routes/project/delete.rs
index c0e1b269..92c6d982 100644
--- a/src/routes/project/delete.rs
+++ b/src/routes/project/delete.rs
@@ -3,8 +3,6 @@ use crate::models;
 use actix_web::{delete, web, Responder, Result};
 use sqlx::PgPool;
 use std::sync::Arc;
-use futures_util::FutureExt;
-use tracing::Instrument;
 use crate::db;
 use crate::models::Project;
 
@@ -15,7 +13,7 @@ pub async fn item(
     path: web::Path<(i32,)>,
     pg_pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
-    /// Get project apps of logged user only
+    // Get project apps of logged user only
     let (id,) = path.into_inner();
 
     let project = db::project::fetch(pg_pool.get_ref(), id)
diff --git a/src/routes/project/get.rs b/src/routes/project/get.rs
index 3cd7fc3e..e5d5a16d 100644
--- a/src/routes/project/get.rs
+++ b/src/routes/project/get.rs
@@ -4,7 +4,6 @@ use crate::models;
 use actix_web::{get, web, Responder, Result};
 use sqlx::PgPool;
 use std::sync::Arc;
-use tracing::Instrument;
 
 #[tracing::instrument(name = "Get logged user project.")]
 #[get("/{id}")]
@@ -13,7 +12,7 @@ pub async fn item(
     path: web::Path<(i32,)>,
     pg_pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
-    /// Get project apps of logged user only
+    // Get project apps of logged user only
     let (id,) = path.into_inner();
 
     db::project::fetch(pg_pool.get_ref(), id)
@@ -35,9 +34,9 @@ pub async fn list(
     path: web::Path<(String,)>,
     pg_pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
-    /// This is admin endpoint, used by a client app, client app is confidential
-    /// it should return projects by user id
-    /// in order to pass validation at external deployment service
+    // This is admin endpoint, used by a client app, client app is confidential
+    // it should return projects by user id
+    // in order to pass validation at external deployment service
     let user_id = path.into_inner().0;
 
     db::project::fetch_by_user(pg_pool.get_ref(), &user_id)
diff --git a/src/routes/project/mod.rs b/src/routes/project/mod.rs
index 6d66205d..05f7de81 100644
--- a/src/routes/project/mod.rs
+++ b/src/routes/project/mod.rs
@@ -5,7 +5,7 @@ pub mod update;
 pub(crate) mod compose;
 pub(crate) mod delete;
 
-pub use add::*;
-pub use update::*;
-pub use deploy::*;
-pub use get::*;
+pub use add::item;
+// pub use update::*;
+// pub use deploy::*;
+// pub use get::*;
diff --git a/src/routes/project/update.rs b/src/routes/project/update.rs
index ecd72b10..4fc85685 100644
--- a/src/routes/project/update.rs
+++ b/src/routes/project/update.rs
@@ -9,7 +9,6 @@ use serde_valid::Validate;
 use sqlx::PgPool;
 use std::sync::Arc;
 use actix_web::web::Bytes;
-use tracing::Instrument;
 use std::str;
 
 #[tracing::instrument(name = "Update project.")]
diff --git a/src/routes/rating/add.rs b/src/routes/rating/add.rs
index 74bc725d..e91dbe6a 100644
--- a/src/routes/rating/add.rs
+++ b/src/routes/rating/add.rs
@@ -4,7 +4,6 @@ use crate::models;
 use crate::db;
 use actix_web::{post, web, Responder, Result};
 use sqlx::PgPool;
-use tracing::Instrument;
 use std::sync::Arc;
 
 // workflow
diff --git a/src/routes/rating/get.rs b/src/routes/rating/get.rs
index 34ef6afd..c34677d0 100644
--- a/src/routes/rating/get.rs
+++ b/src/routes/rating/get.rs
@@ -3,7 +3,6 @@ use crate::helpers::JsonResponse;
 use crate::models;
 use actix_web::{get, web, Responder, Result};
 use sqlx::PgPool;
-use tracing::Instrument;
 
 // workflow
 // add, update, list, get(user_id), ACL,
diff --git a/src/routes/server/delete.rs b/src/routes/server/delete.rs
index cd9a8240..35440ec2 100644
--- a/src/routes/server/delete.rs
+++ b/src/routes/server/delete.rs
@@ -3,7 +3,6 @@ use crate::models;
 use actix_web::{delete, web, Responder, Result};
 use sqlx::PgPool;
 use std::sync::Arc;
-use tracing::Instrument;
 use crate::db;
 use crate::models::Server;
 
@@ -14,7 +13,7 @@ pub async fn item(
     path: web::Path<(i32,)>,
     pg_pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
-    /// Get server apps of logged user only
+    // Get server apps of logged user only
     let (id,) = path.into_inner();
 
     let server = db::server::fetch(pg_pool.get_ref(), id)
diff --git a/src/routes/server/get.rs b/src/routes/server/get.rs
index 23bac5b5..de33e8f8 100644
--- a/src/routes/server/get.rs
+++ b/src/routes/server/get.rs
@@ -4,7 +4,7 @@ use crate::helpers::JsonResponse;
 use crate::models;
 use actix_web::{get, web, Responder, Result};
 use sqlx::PgPool;
-use tracing::Instrument;
+// use tracing::Instrument;
 
 // workflow
 // add, update, list, get(user_id), ACL,
diff --git a/src/routes/server/mod.rs b/src/routes/server/mod.rs
index af796d2d..8ef07d31 100644
--- a/src/routes/server/mod.rs
+++ b/src/routes/server/mod.rs
@@ -3,7 +3,7 @@ pub(crate) mod get;
 pub(crate) mod delete;
 pub(crate) mod update;
 
-pub use get::*;
-pub use add::*;
-pub use update::*;
-pub use delete::*;
+// pub use get::*;
+// pub use add::*;
+// pub use update::*;
+// pub use delete::*;
diff --git a/src/routes/server/update.rs b/src/routes/server/update.rs
index 4f83d1ea..2be6aca0 100644
--- a/src/routes/server/update.rs
+++ b/src/routes/server/update.rs
@@ -2,11 +2,10 @@ use crate::forms;
 use crate::helpers::JsonResponse;
 use crate::models;
 use crate::db;
-use actix_web::{web, web::Data, Responder, Result, post, put};
+use actix_web::{web, web::Data, Responder, Result, put};
 use serde_valid::Validate;
 use sqlx::PgPool;
 use std::sync::Arc;
-use tracing::Instrument;
 use std::ops::Deref;
 
 #[tracing::instrument(name = "Update server.")]

From d68bc5efa552cfc76d1d36ffceccb269c5adfdc4 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Thu, 21 Mar 2024 17:23:19 +0200
Subject: [PATCH 224/284] 30-access-policies

---
 src/routes/rating/get.rs | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/src/routes/rating/get.rs b/src/routes/rating/get.rs
index 34ef6afd..7071dcc8 100644
--- a/src/routes/rating/get.rs
+++ b/src/routes/rating/get.rs
@@ -5,11 +5,6 @@ use actix_web::{get, web, Responder, Result};
 use sqlx::PgPool;
 use tracing::Instrument;
 
-// workflow
-// add, update, list, get(user_id), ACL,
-// ACL - access to func for a user
-// ACL - access to objects for a user
-
 #[tracing::instrument(name = "Get rating.")]
 #[get("/{id}")]
 pub async fn get_handler(

From 2f0507df0d1ad4032bb175c2f72d06af09322262 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Fri, 22 Mar 2024 14:24:01 +0200
Subject: [PATCH 225/284] reconfigure rabbitmq queue

---
 src/console/commands/mq/listener.rs | 37 ++++++++++++++++++-----------
 src/forms/project/payload.rs        |  3 ++-
 src/helpers/compressor.rs           |  7 ------
 src/helpers/dockerhub.rs            |  2 ++
 src/helpers/mq_manager.rs           | 14 ++++-------
 src/routes/project/deploy.rs        | 11 +++++++--
 src/startup.rs                      |  1 +
 7 files changed, 42 insertions(+), 33 deletions(-)

diff --git a/src/console/commands/mq/listener.rs b/src/console/commands/mq/listener.rs
index a9e6c045..3e67a9c6 100644
--- a/src/console/commands/mq/listener.rs
+++ b/src/console/commands/mq/listener.rs
@@ -10,7 +10,6 @@ use crate::db;
 use crate::helpers::mq_manager::MqManager;
 use futures_lite::stream::StreamExt;
 use serde_derive::{Deserialize, Serialize};
-// use crate::forms::project::ProjectForm;
 
 pub struct ListenCommand {
 }
@@ -44,10 +43,13 @@ impl crate::console::commands::CallableTrait for ListenCommand {
 
             println!("Declare exchange");
             let mq_manager = MqManager::try_new(settings.amqp.connection_string())?;
+            // let queue_name = "stacker_listener";
+            let queue_name = "install_progress_m383emvfP9zQKs8lkgSU_Q";
             let consumer_channel= mq_manager
                 .consume(
                     "install_progress",
-                    "install.progress.#"
+                    queue_name,
+                    "install.progress.*.*.*"
                 )
                 .await?;
 
@@ -55,7 +57,7 @@ impl crate::console::commands::CallableTrait for ListenCommand {
             println!("Declare queue");
             let mut consumer = consumer_channel
                 .basic_consume(
-                    "#",
+                    queue_name,
                     "console_listener",
                     BasicConsumeOptions::default(),
                     FieldTable::default(),
@@ -73,32 +75,39 @@ impl crate::console::commands::CallableTrait for ListenCommand {
                     Err(e) => panic!("Invalid UTF-8 sequence: {}", e),
                 };
 
-                println!("incoming data {:?}", s);
-                let statuses = vec!["complete", "paused", "failed"];
+                let statuses = vec![
+                    "completed",
+                    "paused",
+                    "failed",
+                    "in_progress",
+                    "error",
+                    "wait_resume",
+                    "wait_start",
+                    "confirmed"
+                ];
                 match serde_json::from_str::<ProgressMessage>(&s) {
                     Ok(msg) => {
                         println!("message {:?}", s);
-                        // println!("id {:?}", msg.id);
-                        // println!("status {:?}", msg.status);
 
-                        if statuses.contains(&(msg.status.as_str())) {
-                            println!("Process on complete status");
+                        if statuses.contains(&(msg.status.as_ref())) && msg.deploy_id.is_some() {
+                            println!("Update DB on status change ..");
                             let id = msg.deploy_id.unwrap()
                                 .parse::<i32>()
-                                .map_err(|_err| "Could not parse deployment id".to_string() )?;
+                                .map_err(|_err| "Could not parse deployment id".to_string())?;
 
-                            match crate::db::deployment::fetch(
+                            match deployment::fetch(
                                 db_pool.get_ref(), id
                             )
                             .await? {
-
                                 Some(mut row) => {
                                     row.status = msg.status;
                                     row.updated_at = Utc::now();
+                                    println!("Deployment {} updated with status {}",
+                                         &id, &row.status
+                                    );
                                     deployment::update(db_pool.get_ref(), row).await?;
-                                    println!("deployment {} completed successfully", id);
                                 }
-                                None => println!("Deployment record not found in db")
+                                None => println!("Deployment record was not found in db")
                             }
                         }
                     }
diff --git a/src/forms/project/payload.rs b/src/forms/project/payload.rs
index bc8e8fba..d7e84d4b 100644
--- a/src/forms/project/payload.rs
+++ b/src/forms/project/payload.rs
@@ -8,6 +8,7 @@ use serde_valid::Validate;
 #[serde(rename_all = "snake_case")]
 pub struct Payload {
     pub(crate) id: Option<i32>,
+    pub(crate) project_id: Option<i32>,
     pub(crate) user_token: Option<String>,
     pub(crate) user_email: Option<String>,
     #[serde(flatten)]
@@ -30,7 +31,7 @@ impl TryFrom<&models::Project> for Payload {
                 format!("{:?}", err)
             })?;
 
-        project_data.id = Some(project.id.clone());
+        project_data.project_id = Some(project.id);
 
         Ok(project_data)
     }
diff --git a/src/helpers/compressor.rs b/src/helpers/compressor.rs
index 624747d7..c6e12589 100644
--- a/src/helpers/compressor.rs
+++ b/src/helpers/compressor.rs
@@ -10,11 +10,4 @@ pub fn compress(input: &str) -> Vec<u8> {
     compressor.flush().unwrap();
     drop(compressor);
     compressed
-}
-
-pub fn decompress(input: &[u8]) -> String {
-    let mut decompressed = String::new();
-    let mut decompressor = Decompressor::new(input, 4096);
-    decompressor.read_to_string(&mut decompressed).unwrap();
-    decompressed
 }
\ No newline at end of file
diff --git a/src/helpers/dockerhub.rs b/src/helpers/dockerhub.rs
index fa41df77..81f4feb2 100644
--- a/src/helpers/dockerhub.rs
+++ b/src/helpers/dockerhub.rs
@@ -133,6 +133,7 @@ impl<'a> DockerHub<'a> {
                     tracing::debug!("✅ Image is active");
                     active
                 } else {
+                    tracing::debug!("🟥 Image tag is not active");
                     false
                 }
             })
@@ -169,6 +170,7 @@ impl<'a> DockerHub<'a> {
                     tracing::debug!("✅ Image is active");
                     active
                 } else {
+                    tracing::debug!("🟥 Image tag is not active");
                     false
                 }
             })
diff --git a/src/helpers/mq_manager.rs b/src/helpers/mq_manager.rs
index f205f1a5..dfcda23d 100644
--- a/src/helpers/mq_manager.rs
+++ b/src/helpers/mq_manager.rs
@@ -102,11 +102,10 @@ impl MqManager {
     pub async fn consume(
         &self,
         exchange_name: &str,
+        queue_name: &str,
         routing_key: &str,
     ) -> Result<Channel, String> {
 
-        let mut args = FieldTable::default();
-        args.insert("x-expires".into(), AMQPValue::LongUInt(180000));
         let channel = self.create_channel().await?;
 
         channel
@@ -120,18 +119,16 @@ impl MqManager {
                     internal: false,
                     nowait: false,
                 },
-                args
+                FieldTable::default()
             )
             .await
             .expect("Exchange declare failed");
 
         let mut args = FieldTable::default();
-        args.insert("x-expires".into(), AMQPValue::LongUInt(180000));
+        args.insert("x-expires".into(), AMQPValue::LongUInt(3600000));
 
         let queue = channel.queue_declare(
-            // "install_progress_all",
-            "#",
-            // "install_progress_hy181TZa4DaabUZWklsrxw",
+            queue_name,
             QueueDeclareOptions {
                 passive: false,
                 durable: false,
@@ -139,7 +136,6 @@ impl MqManager {
                 auto_delete: true,
                 nowait: false,
             },
-            // FieldTable::default(),
             args,
         )
         .await
@@ -147,7 +143,7 @@ impl MqManager {
 
         let _ = channel
             .queue_bind(
-                queue.name().as_str(),
+                queue_name,
                 exchange_name,
                 routing_key,
                 QueueBindOptions::default(),
diff --git a/src/routes/project/deploy.rs b/src/routes/project/deploy.rs
index f3d242a7..0fa70303 100644
--- a/src/routes/project/deploy.rs
+++ b/src/routes/project/deploy.rs
@@ -95,7 +95,11 @@ pub async fn item(
 
     let result = db::deployment::insert(pg_pool.get_ref(), deployment)
         .await
-        .map(|deployment| deployment)
+        .map(|deployment| {
+            payload.id = Some(deployment.id);
+            deployment
+        }
+        )
         .map_err(|_| {
             JsonResponse::<models::Project>::build().internal_server_error("Internal Server Error")
         });
@@ -194,7 +198,10 @@ pub async fn saved_item(
 
     let result = db::deployment::insert(pg_pool.get_ref(), deployment)
         .await
-        .map(|deployment| deployment)
+        .map(|deployment| {
+            payload.id = Some(deployment.id);
+            deployment
+        })
         .map_err(|_| {
             JsonResponse::<models::Project>::build().internal_server_error("Internal Server Error")
         });
diff --git a/src/startup.rs b/src/startup.rs
index 2c7c4baa..29d0c9b1 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -72,6 +72,7 @@ pub async fn run(
                     ))
                     .wrap(Cors::permissive())
                     .service(crate::routes::project::deploy::item)
+                    .service(crate::routes::project::deploy::saved_item)
                     .service(crate::routes::project::compose::add)
                     .service(crate::routes::project::compose::admin)
                     .service(crate::routes::project::get::item)

From b1295b8df088bf5d52d895674e65fab6302fbbab Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Fri, 22 Mar 2024 21:45:09 +0200
Subject: [PATCH 226/284] docker image tag validation fix

---
 src/console/commands/mq/listener.rs |  5 +++--
 src/forms/project/form.rs           | 12 ++++--------
 src/routes/cloud/delete.rs          |  2 +-
 src/routes/project/deploy.rs        |  8 ++++++--
 src/routes/project/update.rs        |  2 +-
 5 files changed, 15 insertions(+), 14 deletions(-)

diff --git a/src/console/commands/mq/listener.rs b/src/console/commands/mq/listener.rs
index 3e67a9c6..5d4b0c76 100644
--- a/src/console/commands/mq/listener.rs
+++ b/src/console/commands/mq/listener.rs
@@ -43,8 +43,9 @@ impl crate::console::commands::CallableTrait for ListenCommand {
 
             println!("Declare exchange");
             let mq_manager = MqManager::try_new(settings.amqp.connection_string())?;
-            // let queue_name = "stacker_listener";
-            let queue_name = "install_progress_m383emvfP9zQKs8lkgSU_Q";
+            let queue_name = "stacker_listener";
+            // let queue_name = "install_progress_m383emvfP9zQKs8lkgSU_Q";
+            // let queue_name = "install_progress_hy181TZa4DaabUZWklsrxw";
             let consumer_channel= mq_manager
                 .consume(
                     "install_progress",
diff --git a/src/forms/project/form.rs b/src/forms/project/form.rs
index 3924c6c7..840bfaae 100644
--- a/src/forms/project/form.rs
+++ b/src/forms/project/form.rs
@@ -23,19 +23,16 @@ impl TryFrom<&models::Project> for ProjectForm {
 
 impl ProjectForm {
     pub async fn is_readable_docker_image(&self) -> Result<bool, String> {
-        let mut is_active = true;
         for app in &self.custom.web {
             if !app.app.docker_image.is_active().await? {
-                is_active = false;
-                break;
+                return Ok(false);
             }
         }
 
         if let Some(service) = &self.custom.service {
             for app in service {
                 if !app.app.docker_image.is_active().await? {
-                    is_active = false;
-                    break;
+                    return Ok(false);
                 }
             }
         }
@@ -43,12 +40,11 @@ impl ProjectForm {
         if let Some(features) = &self.custom.feature {
             for app in features {
                 if !app.app.docker_image.is_active().await? {
-                    is_active = false;
-                    break;
+                    return Ok(false);
                 }
             }
         }
-        Ok(is_active)
+        Ok(true)
     }
 }
 
diff --git a/src/routes/cloud/delete.rs b/src/routes/cloud/delete.rs
index 94188c71..2654bdeb 100644
--- a/src/routes/cloud/delete.rs
+++ b/src/routes/cloud/delete.rs
@@ -27,7 +27,7 @@ pub async fn item(
                 Some(cloud) => {
                     Ok(cloud)
                 },
-                None => Err(JsonResponse::<models::Cloud>::build().not_found(""))
+                None => Err(JsonResponse::<models::Cloud>::build().not_found("not found"))
             }
         })?;
 
diff --git a/src/routes/project/deploy.rs b/src/routes/project/deploy.rs
index 0fa70303..7cdbb437 100644
--- a/src/routes/project/deploy.rs
+++ b/src/routes/project/deploy.rs
@@ -137,6 +137,7 @@ pub async fn saved_item(
     //let cloud_id = Some(1);
     tracing::debug!("User {:?} is deploying project: {} to cloud: {} ", user, id, cloud_id);
 
+    // Validate project
     let project = db::project::fetch(pg_pool.get_ref(), id)
         .await
         .map_err(|err| JsonResponse::<models::Project>::build().internal_server_error(err))
@@ -145,7 +146,8 @@ pub async fn saved_item(
             None => Err(JsonResponse::<models::Project>::build().not_found("Project not found")),
         })?;
 
-    let id = project.id.clone();
+    // Build compose
+    let id = project.id;
     let dc = DcBuilder::new(project);
     let fc = dc.build().map_err(|err| {
         JsonResponse::<models::Project>::build().internal_server_error(err)
@@ -181,6 +183,7 @@ pub async fn saved_item(
     // let mut payload = forms::project::Payload::default();
     let mut payload = forms::project::Payload::try_from(&dc.project)
         .map_err(|err| JsonResponse::<models::Project>::build().bad_request(err))?;
+
     payload.server = Some(server.into());
     payload.cloud  = Some(cloud.into());
     payload.user_token = Some(user.id.clone());
@@ -188,7 +191,7 @@ pub async fn saved_item(
     // let compressed = fc.unwrap_or("".to_string());
     payload.docker_compose = Some(compress(fc.as_str()));
 
-
+    // Store deployment attempts into deployment table in db
     let json_request = dc.project.body.clone();
     let deployment = models::Deployment::new(
         dc.project.id,
@@ -209,6 +212,7 @@ pub async fn saved_item(
     tracing::debug!("Save deployment result: {:?}", result);
     tracing::debug!("Send project data <<<<<<<<<<<>>>>>>>>>>>>>>>>{:?}", payload);
 
+    // Send Payload
     mq_manager
         .publish(
             "install".to_string(),
diff --git a/src/routes/project/update.rs b/src/routes/project/update.rs
index 4fc85685..9bddfb9a 100644
--- a/src/routes/project/update.rs
+++ b/src/routes/project/update.rs
@@ -47,7 +47,7 @@ pub async fn item(
 
     let project_name = form.custom.custom_stack_code.clone();
 
-    if !form.is_readable_docker_image().await.is_ok() {
+    if Ok(false) == form.is_readable_docker_image().await {
         return Err(JsonResponse::<models::Project>::build().bad_request("Can not access docker image"));
     }
 

From 70cbfafe4a03cde5f53937b1306af52bf9b5e7ee Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Fri, 22 Mar 2024 22:34:40 +0200
Subject: [PATCH 227/284] 30-access-policies

---
 src/forms/project/form.rs    | 23 -----------------------
 src/routes/project/add.rs    | 28 ++++++++++++++++++++--------
 src/routes/project/update.rs |  2 ++
 src/startup.rs               |  4 ++--
 4 files changed, 24 insertions(+), 33 deletions(-)

diff --git a/src/forms/project/form.rs b/src/forms/project/form.rs
index d2498cf4..7d3a3a65 100644
--- a/src/forms/project/form.rs
+++ b/src/forms/project/form.rs
@@ -52,26 +52,3 @@ impl ProjectForm {
         Ok(is_active)
     }
 }
-
-pub(crate) async fn body_into_form(body: Bytes) -> actix_web::Result<forms::project::ProjectForm, Error> {
-    let body_bytes = actix_web::body::to_bytes(body).await.unwrap();
-    let body_str = str::from_utf8(&body_bytes)
-        .map_err(|err| JsonResponse::<forms::project::ProjectForm>::build().internal_server_error(err.to_string()))?;
-    let deserializer = &mut serde_json::Deserializer::from_str(body_str);
-    serde_path_to_error::deserialize(deserializer)
-        .map_err(|err| {
-            let msg = format!("{}:{:?}", err.path().to_string(), err);
-            JsonResponse::<forms::project::ProjectForm>::build().bad_request(msg)
-        })
-        .and_then(|mut form: forms::project::ProjectForm| {
-            if !form.validate().is_ok() {
-                let errors = form.validate().unwrap_err().to_string();
-                let err_msg = format!("Invalid data received {:?}", &errors);
-                tracing::debug!(err_msg);
-
-                return Err(JsonResponse::<models::Project>::build().form_error(errors));
-            }
-
-            Ok(form)
-        })
-}
diff --git a/src/routes/project/add.rs b/src/routes/project/add.rs
index e1b84bc9..ee8c67f7 100644
--- a/src/routes/project/add.rs
+++ b/src/routes/project/add.rs
@@ -12,24 +12,37 @@ use sqlx::PgPool;
 use std::sync::Arc;
 use std::str::FromStr;
 use std::str;
+use serde_valid::Validate;
 
 #[tracing::instrument(name = "Add project.")]
 #[post("")]
 pub async fn item(
-    body: Bytes,
+    request_json: web::Json<serde_json::Value>,
     user: web::ReqData<Arc<models::User>>,
     pg_pool: Data<PgPool>,
 ) -> Result<impl Responder> {
     // @todo ACL
-    let form = forms::project::form::body_into_form(body.clone()).await?;
+    let request_json = request_json.into_inner(); //todo
+    let form = serde_json::from_value(request_json.clone())
+        .map_err(|err| {
+            let msg = format!("{}", err); //todo JsonReponse::BadRequest::from(err)
+            JsonResponse::<forms::project::ProjectForm>::build().bad_request(msg)
+        })
+        .and_then(|mut form: forms::project::ProjectForm| {
+            if !form.validate().is_ok() {
+                let errors = form.validate().unwrap_err().to_string();
+                let err_msg = format!("Invalid data received {:?}", &errors);
+                tracing::debug!(err_msg);
+
+                return Err(JsonResponse::<models::Project>::build().form_error(errors));
+            }
+
+            Ok(form)
+        })?;
+
     let project_name = form.custom.custom_stack_code.clone();
 
-    let body_bytes = actix_web::body::to_bytes(body).await.unwrap();
-    let body_str = str::from_utf8(&body_bytes)
-        .map_err(|err| JsonResponse::<forms::project::ProjectForm>::build().internal_server_error(err.to_string()))?;
-    let request_json = Value::from_str(body_str).unwrap();
     tracing::debug!("Request json: {:?}", request_json);
-
     let body: Value = serde_json::to_value::<forms::project::ProjectForm>(form)
         .or(serde_json::to_value::<forms::project::ProjectForm>(forms::project::ProjectForm::default()))
         .unwrap();
@@ -48,4 +61,3 @@ pub async fn item(
             JsonResponse::<models::Project>::build().internal_server_error("Internal Server Error")
         })
 }
-
diff --git a/src/routes/project/update.rs b/src/routes/project/update.rs
index ecd72b10..bfd82b66 100644
--- a/src/routes/project/update.rs
+++ b/src/routes/project/update.rs
@@ -12,6 +12,7 @@ use actix_web::web::Bytes;
 use tracing::Instrument;
 use std::str;
 
+/*
 #[tracing::instrument(name = "Update project.")]
 #[put("/{id}")]
 pub async fn item(
@@ -74,3 +75,4 @@ pub async fn item(
             JsonResponse::<models::Project>::build().internal_server_error("")
         })
 }
+*/
diff --git a/src/startup.rs b/src/startup.rs
index 72d66c6f..9c31afd4 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -31,7 +31,7 @@ pub async fn run(
 
     let authorization = middleware::authorization::try_new(settings.database.connection_string()).await?;
     let json_config = web::JsonConfig::default()
-        .error_handler(|err, req| {
+        .error_handler(|err, req| { //todo
             let msg: String = match err {
                  error::JsonPayloadError::Deserialize(err) => format!("{{\"kind\":\"deserialize\",\"line\":{}, \"column\":{}, \"msg\":\"{}\"}}", err.line(), err.column(), err),
                  _ => format!("{{\"kind\":\"other\",\"msg\":\"{}\"}}", err)
@@ -78,7 +78,7 @@ pub async fn run(
                     .service(crate::routes::project::get::item)
                     .service(crate::routes::project::get::list)
                     .service(crate::routes::project::add::item)
-                    .service(crate::routes::project::update::item)
+                    //.service(crate::routes::project::update::item) todo
                     .service(crate::routes::project::delete::item),
             )
             .service(

From db44167062993dad3840d0f8eb504385b22afe34 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 23 Mar 2024 16:24:43 +0200
Subject: [PATCH 228/284] 30-access-policies project add

---
 src/helpers/json.rs       | 22 +++++++++++++++-------
 src/routes/project/add.rs | 29 +++++++----------------------
 2 files changed, 22 insertions(+), 29 deletions(-)

diff --git a/src/helpers/json.rs b/src/helpers/json.rs
index ee209c10..ac77cbe9 100644
--- a/src/helpers/json.rs
+++ b/src/helpers/json.rs
@@ -1,7 +1,9 @@
 use actix_web::error::{ErrorBadRequest, ErrorConflict, ErrorInternalServerError, ErrorNotFound, ErrorUnauthorized};
 use actix_web::web::Json;
-use actix_web::Error;
+use actix_web::Error as ActixError;
 use serde_derive::Serialize;
+use std::error::Error as StdError;
+use std::convert::From;
 
 #[derive(Serialize)]
 pub(crate) struct JsonResponse<T> {
@@ -70,33 +72,33 @@ where
     pub(crate) fn bad_request<I: Into<String>>(
         self,
         msg: I,
-    ) -> Error {
+    ) -> ActixError {
         ErrorBadRequest(self.set_msg(msg).to_string())
     }
 
-    pub(crate) fn form_error(self, msg: String) -> Error {
+    pub(crate) fn form_error(self, msg: String) -> ActixError {
         ErrorBadRequest(msg)
     }
 
-    pub(crate) fn not_found<I: Into<String>>(self, msg: I) -> Error {
+    pub(crate) fn not_found<I: Into<String>>(self, msg: I) -> ActixError {
         ErrorNotFound(self.set_msg(msg).to_string())
     }
 
     pub(crate) fn internal_server_error<I: Into<String>>(
         self,
         msg: I,
-    ) -> Error {
+    ) -> ActixError {
         ErrorInternalServerError(self.set_msg(msg).to_string())
     }
 
     pub(crate) fn unauthorized<I: Into<String>>(
         self,
         msg: I,
-    ) -> Error {
+    ) -> ActixError {
         ErrorUnauthorized(self.set_msg(msg).to_string())
     }
  
-    pub(crate) fn conflict<I: Into<String>>(self, msg: I) -> Error {
+    pub(crate) fn conflict<I: Into<String>>(self, msg: I) -> ActixError {
         ErrorConflict(self.set_msg(msg).to_string())
     }
 }
@@ -109,3 +111,9 @@ where
         JsonResponseBuilder::default()
     }
 }
+
+impl JsonResponse<String> {
+    pub fn err_bad_request<E: StdError>(e: E) -> ActixError {
+        JsonResponse::<String>::build().bad_request( e.to_string())
+    }
+}
diff --git a/src/routes/project/add.rs b/src/routes/project/add.rs
index ee8c67f7..892d31aa 100644
--- a/src/routes/project/add.rs
+++ b/src/routes/project/add.rs
@@ -10,39 +10,24 @@ use actix_web::{
 use serde_json::Value;
 use sqlx::PgPool;
 use std::sync::Arc;
-use std::str::FromStr;
-use std::str;
 use serde_valid::Validate;
 
 #[tracing::instrument(name = "Add project.")]
 #[post("")]
 pub async fn item(
-    request_json: web::Json<serde_json::Value>,
+    web::Json(request_json): web::Json<serde_json::Value>,
     user: web::ReqData<Arc<models::User>>,
     pg_pool: Data<PgPool>,
 ) -> Result<impl Responder> {
     // @todo ACL
-    let request_json = request_json.into_inner(); //todo
-    let form = serde_json::from_value(request_json.clone())
-        .map_err(|err| {
-            let msg = format!("{}", err); //todo JsonReponse::BadRequest::from(err)
-            JsonResponse::<forms::project::ProjectForm>::build().bad_request(msg)
-        })
-        .and_then(|mut form: forms::project::ProjectForm| {
-            if !form.validate().is_ok() {
-                let errors = form.validate().unwrap_err().to_string();
-                let err_msg = format!("Invalid data received {:?}", &errors);
-                tracing::debug!(err_msg);
-
-                return Err(JsonResponse::<models::Project>::build().form_error(errors));
-            }
-
-            Ok(form)
-        })?;
+    let form: forms::project::ProjectForm= serde_json::from_value(request_json.clone())
+        .map_err(JsonResponse::err_bad_request)?;
+    if !form.validate().is_ok() {
+        let errors = form.validate().unwrap_err();
+        return Err(JsonResponse::err_bad_request(errors));
+    }
 
     let project_name = form.custom.custom_stack_code.clone();
-
-    tracing::debug!("Request json: {:?}", request_json);
     let body: Value = serde_json::to_value::<forms::project::ProjectForm>(form)
         .or(serde_json::to_value::<forms::project::ProjectForm>(forms::project::ProjectForm::default()))
         .unwrap();

From 63888bd8e4060b8bdb6843ecc003d2045ee2606b Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 23 Mar 2024 16:34:50 +0200
Subject: [PATCH 229/284] 30-access-policies POST /project

---
 src/routes/project/add.rs | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/routes/project/add.rs b/src/routes/project/add.rs
index 892d31aa..0c170665 100644
--- a/src/routes/project/add.rs
+++ b/src/routes/project/add.rs
@@ -1,10 +1,10 @@
 use crate::db;
-use crate::forms;
+use crate::forms::project::ProjectForm;
 use crate::helpers::JsonResponse;
 use crate::models;
 use actix_web::{
     post, web,
-    web::{Bytes, Data},
+    web::{Data},
     Responder, Result,
 };
 use serde_json::Value;
@@ -20,7 +20,7 @@ pub async fn item(
     pg_pool: Data<PgPool>,
 ) -> Result<impl Responder> {
     // @todo ACL
-    let form: forms::project::ProjectForm= serde_json::from_value(request_json.clone())
+    let form: ProjectForm= serde_json::from_value(request_json.clone())
         .map_err(JsonResponse::err_bad_request)?;
     if !form.validate().is_ok() {
         let errors = form.validate().unwrap_err();
@@ -28,8 +28,8 @@ pub async fn item(
     }
 
     let project_name = form.custom.custom_stack_code.clone();
-    let body: Value = serde_json::to_value::<forms::project::ProjectForm>(form)
-        .or(serde_json::to_value::<forms::project::ProjectForm>(forms::project::ProjectForm::default()))
+    let body: Value = serde_json::to_value::<ProjectForm>(form)
+        .or(serde_json::to_value::<ProjectForm>(ProjectForm::default()))
         .unwrap();
 
     let project = models::Project::new(

From 1637f6f0b5e623a024bc75d01c62645238425285 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 23 Mar 2024 21:11:07 +0200
Subject: [PATCH 230/284] 30-access-policies POST/PUT /project

---
 src/helpers/json.rs          | 27 +++++++++++++---------
 src/routes/project/add.rs    |  6 ++---
 src/routes/project/update.rs | 43 ++++++++++++++----------------------
 src/startup.rs               |  2 +-
 4 files changed, 37 insertions(+), 41 deletions(-)

diff --git a/src/helpers/json.rs b/src/helpers/json.rs
index ac77cbe9..36f8d39f 100644
--- a/src/helpers/json.rs
+++ b/src/helpers/json.rs
@@ -1,8 +1,7 @@
 use actix_web::error::{ErrorBadRequest, ErrorConflict, ErrorInternalServerError, ErrorNotFound, ErrorUnauthorized};
 use actix_web::web::Json;
-use actix_web::Error as ActixError;
+use actix_web::Error;
 use serde_derive::Serialize;
-use std::error::Error as StdError;
 use std::convert::From;
 
 #[derive(Serialize)]
@@ -72,33 +71,33 @@ where
     pub(crate) fn bad_request<I: Into<String>>(
         self,
         msg: I,
-    ) -> ActixError {
+    ) -> Error {
         ErrorBadRequest(self.set_msg(msg).to_string())
     }
 
-    pub(crate) fn form_error(self, msg: String) -> ActixError {
+    pub(crate) fn form_error(self, msg: String) -> Error {
         ErrorBadRequest(msg)
     }
 
-    pub(crate) fn not_found<I: Into<String>>(self, msg: I) -> ActixError {
+    pub(crate) fn not_found<I: Into<String>>(self, msg: I) -> Error {
         ErrorNotFound(self.set_msg(msg).to_string())
     }
 
     pub(crate) fn internal_server_error<I: Into<String>>(
         self,
         msg: I,
-    ) -> ActixError {
+    ) -> Error {
         ErrorInternalServerError(self.set_msg(msg).to_string())
     }
 
     pub(crate) fn unauthorized<I: Into<String>>(
         self,
         msg: I,
-    ) -> ActixError {
+    ) -> Error {
         ErrorUnauthorized(self.set_msg(msg).to_string())
     }
  
-    pub(crate) fn conflict<I: Into<String>>(self, msg: I) -> ActixError {
+    pub(crate) fn conflict<I: Into<String>>(self, msg: I) -> Error {
         ErrorConflict(self.set_msg(msg).to_string())
     }
 }
@@ -113,7 +112,15 @@ where
 }
 
 impl JsonResponse<String> {
-    pub fn err_bad_request<E: StdError>(e: E) -> ActixError {
-        JsonResponse::<String>::build().bad_request( e.to_string())
+    pub fn bad_request<I: Into<String>>(msg: I) -> Error {
+        JsonResponse::<String>::build().bad_request( msg.into())
+    }
+
+    pub fn internal_server_error<I: Into<String>>(msg: I) -> Error {
+        JsonResponse::<String>::build().internal_server_error( msg.into())
+    }
+
+    pub fn not_found<I: Into<String>>(msg: I) -> Error {
+        JsonResponse::<String>::build().not_found(msg.into())
     }
 }
diff --git a/src/routes/project/add.rs b/src/routes/project/add.rs
index 0c170665..683e1d33 100644
--- a/src/routes/project/add.rs
+++ b/src/routes/project/add.rs
@@ -21,10 +21,10 @@ pub async fn item(
 ) -> Result<impl Responder> {
     // @todo ACL
     let form: ProjectForm= serde_json::from_value(request_json.clone())
-        .map_err(JsonResponse::err_bad_request)?;
+        .map_err(|err| JsonResponse::bad_request(err.to_string()))?;
     if !form.validate().is_ok() {
         let errors = form.validate().unwrap_err();
-        return Err(JsonResponse::err_bad_request(errors));
+        return Err(JsonResponse::bad_request(errors.to_string()));
     }
 
     let project_name = form.custom.custom_stack_code.clone();
@@ -43,6 +43,6 @@ pub async fn item(
         .await
         .map(|project| JsonResponse::build().set_item(project).ok("Ok"))
         .map_err(|_| {
-            JsonResponse::<models::Project>::build().internal_server_error("Internal Server Error")
+            JsonResponse::internal_server_error("Internal Server Error")
         })
 }
diff --git a/src/routes/project/update.rs b/src/routes/project/update.rs
index bfd82b66..312c4226 100644
--- a/src/routes/project/update.rs
+++ b/src/routes/project/update.rs
@@ -1,60 +1,51 @@
 use std::str::FromStr;
-use crate::forms;
+use crate::forms::project::ProjectForm;
 use crate::helpers::JsonResponse;
 use crate::models;
 use crate::db;
-use actix_web::{web, web::Data, Responder, Result, put};
+use actix_web::{web, Responder, Result, put};
 use serde_json::Value;
 use serde_valid::Validate;
 use sqlx::PgPool;
 use std::sync::Arc;
-use actix_web::web::Bytes;
 use tracing::Instrument;
 use std::str;
 
-/*
 #[tracing::instrument(name = "Update project.")]
 #[put("/{id}")]
 pub async fn item(
     path: web::Path<(i32,)>,
-    body: Bytes,
+    web::Json(request_json): web::Json<serde_json::Value>,
     user: web::ReqData<Arc<models::User>>,
-    pg_pool: Data<PgPool>,
+    pg_pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
     let id = path.0;
     let mut project = db::project::fetch(pg_pool.get_ref(), id)
         .await
-        .map_err(|err| JsonResponse::<models::Project>::build().internal_server_error(err))
+        .map_err(JsonResponse::internal_server_error)
         .and_then(|project| match project {
             Some(project) if project.user_id != user.id => {
-                Err(JsonResponse::<models::Project>::build().bad_request("Project not found"))
+                Err(JsonResponse::bad_request("Project not found"))
             }
             Some(project) => Ok(project),
-            None => Err(JsonResponse::<models::Project>::build().not_found("Project not found")),
+            None => Err(JsonResponse::not_found("Project not found")),
         })?;
 
-    let body_bytes = actix_web::body::to_bytes(body.clone()).await.unwrap();
-    let body_str = str::from_utf8(&body_bytes)
-        .map_err(|err| JsonResponse::<forms::project::ProjectForm>::build().internal_server_error(err.to_string()))?;
-    let request_json = Value::from_str(body_str)?;
-    tracing::debug!("Request json: {:?}", request_json);
-
     // @todo ACL
-    let form = forms::project::form::body_into_form(body.clone()).await?;
-    tracing::debug!("form data: {:?}", form);
-
-    if let Err(errors) = form.validate() {
-        return Err(JsonResponse::<models::Project>::build().form_error(errors.to_string()));
+    let form: ProjectForm= serde_json::from_value(request_json.clone())
+        .map_err(|err| JsonResponse::bad_request(err.to_string()))?;
+    if !form.validate().is_ok() {
+        let errors = form.validate().unwrap_err();
+        return Err(JsonResponse::bad_request(errors.to_string()));
     }
 
     let project_name = form.custom.custom_stack_code.clone();
-
     if !form.is_readable_docker_image().await.is_ok() {
-        return Err(JsonResponse::<models::Project>::build().bad_request("Can not access docker image"));
+        return Err(JsonResponse::bad_request("Can not access docker image"));
     }
 
-    let body: Value = serde_json::to_value::<forms::project::ProjectForm>(form)
-        .or(serde_json::to_value::<forms::project::ProjectForm>(forms::project::ProjectForm::default()))
+    let body: Value = serde_json::to_value::<ProjectForm>(form)
+        .or(serde_json::to_value::<ProjectForm>(ProjectForm::default()))
         .unwrap();
 
 
@@ -62,7 +53,6 @@ pub async fn item(
     project.body = body;
     project.request_json = request_json;
 
-
     db::project::update(pg_pool.get_ref(), project)
         .await
         .map(|project| {
@@ -72,7 +62,6 @@ pub async fn item(
         })
         .map_err(|err| {
             tracing::error!("Failed to execute query: {:?}", err);
-            JsonResponse::<models::Project>::build().internal_server_error("")
+            JsonResponse::internal_server_error("")
         })
 }
-*/
diff --git a/src/startup.rs b/src/startup.rs
index 9c31afd4..35ee007e 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -78,7 +78,7 @@ pub async fn run(
                     .service(crate::routes::project::get::item)
                     .service(crate::routes::project::get::list)
                     .service(crate::routes::project::add::item)
-                    //.service(crate::routes::project::update::item) todo
+                    .service(crate::routes::project::update::item) 
                     .service(crate::routes::project::delete::item),
             )
             .service(

From 304f54be296624b7f2d1c0ea3f08515885bfd42b Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 23 Mar 2024 21:34:44 +0200
Subject: [PATCH 231/284] 30-access-policies admin get project

---
 src/routes/project/get.rs | 11 +++++------
 src/startup.rs            |  8 +++++++-
 2 files changed, 12 insertions(+), 7 deletions(-)

diff --git a/src/routes/project/get.rs b/src/routes/project/get.rs
index 3cd7fc3e..7544a797 100644
--- a/src/routes/project/get.rs
+++ b/src/routes/project/get.rs
@@ -13,18 +13,17 @@ pub async fn item(
     path: web::Path<(i32,)>,
     pg_pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
-    /// Get project apps of logged user only
-    let (id,) = path.into_inner();
+    let id = path.0;
 
     db::project::fetch(pg_pool.get_ref(), id)
         .await
-        .map_err(|err| JsonResponse::<models::Project>::build().internal_server_error(err))
+        .map_err(|err| JsonResponse::internal_server_error(err.to_string()))
         .and_then(|project| match project {
             Some(project) if project.user_id != user.id => {
-                Err(JsonResponse::<models::Project>::build().not_found("not found"))
+                Err(JsonResponse::not_found("not found"))
             }
             Some(project) => Ok(JsonResponse::build().set_item(Some(project)).ok("OK")),
-            None => Err(JsonResponse::<models::Project>::build().not_found("not found")),
+            None => Err(JsonResponse::not_found("not found")),
         })
 }
 
@@ -42,6 +41,6 @@ pub async fn list(
 
     db::project::fetch_by_user(pg_pool.get_ref(), &user_id)
         .await
-        .map_err(|err| JsonResponse::<models::Project>::build().internal_server_error(err))
+        .map_err(|err| JsonResponse::internal_server_error(err))
         .map(|projects| JsonResponse::build().set_list(projects).ok("OK"))
 }
diff --git a/src/startup.rs b/src/startup.rs
index 35ee007e..b0d788b4 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -76,11 +76,17 @@ pub async fn run(
                     .service(crate::routes::project::compose::add)
                     .service(crate::routes::project::compose::admin)
                     .service(crate::routes::project::get::item)
-                    .service(crate::routes::project::get::list)
                     .service(crate::routes::project::add::item)
                     .service(crate::routes::project::update::item) 
                     .service(crate::routes::project::delete::item),
             )
+            .service(
+                web::scope("/admin")
+                    .service(
+                        web::scope("/project")
+                            .service(crate::routes::project::get::list)
+                    )
+            )
             .service(
                 web::scope("/cloud")
                     .service(crate::routes::cloud::get::item)

From c00e09356b57aceef8c3a5f44f8e11fd19b23502 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 23 Mar 2024 21:38:48 +0200
Subject: [PATCH 232/284] 30-access-policies admin routes grouped

---
 src/routes/project/get.rs |  2 +-
 src/startup.rs            | 20 +++++++-------------
 2 files changed, 8 insertions(+), 14 deletions(-)

diff --git a/src/routes/project/get.rs b/src/routes/project/get.rs
index 7544a797..7c0ca553 100644
--- a/src/routes/project/get.rs
+++ b/src/routes/project/get.rs
@@ -29,7 +29,7 @@ pub async fn item(
 
 #[tracing::instrument(name = "Get user's project list.")]
 #[get("/user/{id}")]
-pub async fn list(
+pub async fn admin_list(
     user: web::ReqData<Arc<models::User>>,
     path: web::Path<(String,)>,
     pg_pool: web::Data<PgPool>,
diff --git a/src/startup.rs b/src/startup.rs
index b0d788b4..56a317f8 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -54,12 +54,6 @@ pub async fn run(
                     .service(routes::client::enable_handler)
                     .service(routes::client::disable_handler),
             )
-            .service(
-                web::scope("/admin/client")
-                    .service(routes::client::admin_enable_handler)
-                    .service(routes::client::admin_update_handler)
-                    .service(routes::client::admin_disable_handler),
-            )
             .service(
                 web::scope("/test")
                     .service(routes::test::deploy::handler)
@@ -84,7 +78,13 @@ pub async fn run(
                 web::scope("/admin")
                     .service(
                         web::scope("/project")
-                            .service(crate::routes::project::get::list)
+                            .service(crate::routes::project::get::admin_list)
+                    )
+                    .service(
+                        web::scope("/client")
+                            .service(routes::client::admin_enable_handler)
+                            .service(routes::client::admin_update_handler)
+                            .service(routes::client::admin_disable_handler),
                     )
             )
             .service(
@@ -103,12 +103,6 @@ pub async fn run(
                     .service(crate::routes::server::update::item)
                     .service(crate::routes::server::delete::item),
             )
-            // @todo stack renamed to project
-            // .service(
-            //     web::scope("/admin/project")
-            //         .service(routes::project::get::admin_item)
-            //         .service(routes::project::get::admin_list)
-            // )
             .app_data(json_config.clone())
             .app_data(pg_pool.clone())
             .app_data(mq_manager.clone())

From a720a0339193622941452534a1e5c4ffd6758475 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Tue, 26 Mar 2024 23:07:45 +0200
Subject: [PATCH 233/284] casbin-debug command

---
 src/console/commands/debug/casbin.rs | 21 +++++++++++++++++++++
 src/console/commands/debug/mod.rs    |  2 ++
 src/console/main.rs                  | 11 +++++++++++
 3 files changed, 34 insertions(+)
 create mode 100644 src/console/commands/debug/casbin.rs

diff --git a/src/console/commands/debug/casbin.rs b/src/console/commands/debug/casbin.rs
new file mode 100644
index 00000000..4662c0f8
--- /dev/null
+++ b/src/console/commands/debug/casbin.rs
@@ -0,0 +1,21 @@
+use crate::configuration::get_configuration;
+use actix_web::{rt, post, web, HttpResponse, Result, http::header::ContentType};
+
+pub struct CasbinCommand {
+    action: String,
+    method: String,
+    subject: String
+}
+
+impl CasbinCommand {
+    pub fn new(action: String, method: String, subject: String) -> Self {
+        Self { action, method, subject }
+    }
+}
+
+impl crate::console::commands::CallableTrait for CasbinCommand {
+    fn call(&self) -> Result<(), Box<dyn std::error::Error>> {
+        println!("action: {}, method: {}, subject: {}", self.action, self.method, self.subject);
+        Ok(())
+    }
+}
diff --git a/src/console/commands/debug/mod.rs b/src/console/commands/debug/mod.rs
index 5f50076c..48862746 100644
--- a/src/console/commands/debug/mod.rs
+++ b/src/console/commands/debug/mod.rs
@@ -1,3 +1,5 @@
 mod json;
+mod casbin;
 
 pub use json::*;
+pub use casbin::*;
diff --git a/src/console/main.rs b/src/console/main.rs
index 257205a2..8521d04c 100644
--- a/src/console/main.rs
+++ b/src/console/main.rs
@@ -40,6 +40,14 @@ enum DebugCommands {
         #[arg(long)]
         payload: String,
     },
+    Casbin {
+        #[arg(long)]
+        action: String,
+        #[arg(long)]
+        method: String,
+        #[arg(long)]
+        subject: String,
+    },
 }
 
 #[derive(Debug, Subcommand)]
@@ -65,6 +73,9 @@ fn get_command(cli: Cli) -> Result<Box<dyn stacker::console::commands::CallableT
             DebugCommands::Json { line, column, payload } => Ok(Box::new(
                 stacker::console::commands::debug::JsonCommand::new(line, column, payload),
             )),
+            DebugCommands::Casbin { action, method, subject } => Ok(Box::new(
+                stacker::console::commands::debug::CasbinCommand::new(action, method, subject),
+            )),
         },
         Commands::MQ { command} => match command {
             AppMqCommands::Listen {} => Ok(Box::new(

From ba205923c665d2731670a723612f740be9a218eb Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Tue, 26 Mar 2024 23:34:31 +0200
Subject: [PATCH 234/284] casbin-debug casbin main logic

---
 src/console/commands/debug/casbin.rs | 34 ++++++++++++++++++++++++----
 src/console/main.rs                  |  6 ++---
 2 files changed, 32 insertions(+), 8 deletions(-)

diff --git a/src/console/commands/debug/casbin.rs b/src/console/commands/debug/casbin.rs
index 4662c0f8..6484ccab 100644
--- a/src/console/commands/debug/casbin.rs
+++ b/src/console/commands/debug/casbin.rs
@@ -1,21 +1,45 @@
 use crate::configuration::get_configuration;
 use actix_web::{rt, post, web, HttpResponse, Result, http::header::ContentType};
+use crate::middleware;
+use actix_casbin_auth::casbin::CoreApi;
+use sqlx::PgPool;
 
 pub struct CasbinCommand {
     action: String,
-    method: String,
+    path: String,
     subject: String
 }
 
 impl CasbinCommand {
-    pub fn new(action: String, method: String, subject: String) -> Self {
-        Self { action, method, subject }
+    pub fn new(action: String, path: String, subject: String) -> Self {
+        Self { action, path, subject }
     }
 }
 
 impl crate::console::commands::CallableTrait for CasbinCommand {
     fn call(&self) -> Result<(), Box<dyn std::error::Error>> {
-        println!("action: {}, method: {}, subject: {}", self.action, self.method, self.subject);
-        Ok(())
+        rt::System::new().block_on(async {
+            let settings = get_configuration().expect("Failed to read configuration.");
+            let db_pool = PgPool::connect(&settings.database.connection_string())
+                .await
+                .expect("Failed to connect to database.");
+
+            let settings = web::Data::new(settings);
+            let db_pool = web::Data::new(db_pool);
+
+            let mut authorizationService = middleware::authorization::try_new(settings.database.connection_string()).await?;
+            let casbin_enforcer = authorizationService.get_enforcer();
+            let mut lock = casbin_enforcer.write().await;
+            match lock.enforce_mut(vec![self.subject.clone(), self.path.clone(), self.action.clone()]) {
+                Ok(true) => println!("TRUE"),
+                Ok(false) => println!("FALSE"),
+                Err(err) => {
+                    println!("err {err:?}");
+                }
+            }
+            drop(lock);
+
+            Ok(())
+        })
     }
 }
diff --git a/src/console/main.rs b/src/console/main.rs
index 8521d04c..f339b30c 100644
--- a/src/console/main.rs
+++ b/src/console/main.rs
@@ -44,7 +44,7 @@ enum DebugCommands {
         #[arg(long)]
         action: String,
         #[arg(long)]
-        method: String,
+        path: String,
         #[arg(long)]
         subject: String,
     },
@@ -73,8 +73,8 @@ fn get_command(cli: Cli) -> Result<Box<dyn stacker::console::commands::CallableT
             DebugCommands::Json { line, column, payload } => Ok(Box::new(
                 stacker::console::commands::debug::JsonCommand::new(line, column, payload),
             )),
-            DebugCommands::Casbin { action, method, subject } => Ok(Box::new(
-                stacker::console::commands::debug::CasbinCommand::new(action, method, subject),
+            DebugCommands::Casbin { action, path, subject } => Ok(Box::new(
+                stacker::console::commands::debug::CasbinCommand::new(action, path, subject),
             )),
         },
         Commands::MQ { command} => match command {

From 7dde11789943add5d8d7b8aca2f9fd3f8657eff7 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Wed, 27 Mar 2024 12:31:17 +0200
Subject: [PATCH 235/284] security for db records, initial steps, key is
 hardcoded for testing purposes

---
 .env                                          |   3 +-
 Cargo.lock                                    | 640 +++++++++++-------
 Cargo.toml                                    |   3 +
 docker-compose.yml                            |  22 +
 docker/dev/.env                               |   5 +-
 docker/dev/docker-compose.yml                 |  23 +
 src/forms/cloud.rs                            | 124 +++-
 src/helpers/cloud/mod.rs                      |   2 +
 src/helpers/cloud/security.rs                 | 135 ++++
 src/helpers/mod.rs                            |   3 +
 .../authentication/method/f_oauth.rs          |  11 +-
 src/models/cloud.rs                           |  35 +
 src/routes/cloud/add.rs                       |  12 +-
 src/routes/cloud/get.rs                       |  25 +-
 src/routes/cloud/update.rs                    |   2 +
 src/routes/project/compose.rs                 |   2 +-
 src/routes/server/get.rs                      |  13 +-
 src/routes/server/update.rs                   |   2 -
 src/startup.rs                                |   5 +-
 tests/mock_data/cloud.json                    |   3 +-
 20 files changed, 777 insertions(+), 293 deletions(-)
 create mode 100644 src/helpers/cloud/mod.rs
 create mode 100644 src/helpers/cloud/security.rs

diff --git a/.env b/.env
index 14bdc640..1dcbed0b 100644
--- a/.env
+++ b/.env
@@ -4,4 +4,5 @@ DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/stacker
 POSTGRES_USER=postgres
 POSTGRES_PASSWORD=postgres
 POSTGRES_DB=stacker
-POSTGRES_PORT=5432
\ No newline at end of file
+POSTGRES_PORT=5432
+SECURITY_KEY=SECURITY_KEY_SHOULD_BE_OF_LEN_32
diff --git a/Cargo.lock b/Cargo.lock
index f04999e9..b5d6160f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5,7 +5,7 @@ version = 3
 [[package]]
 name = "actix-casbin-auth"
 version = "0.4.4"
-source = "git+https://github.com/smart--petea/actix-casbin-auth.git?branch=dirty-master#5b76cc4e0df68ccee02c563b947a788ab1cb5193"
+source = "git+https://github.com/smart--petea/actix-casbin-auth.git?branch=dirty-master#278bd574038617171a6493aef3d4d4fdef324602"
 dependencies = [
  "actix-service",
  "actix-web",
@@ -20,7 +20,7 @@ version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a"
 dependencies = [
- "bitflags 2.4.2",
+ "bitflags 2.5.0",
  "bytes",
  "futures-core",
  "futures-sink",
@@ -56,9 +56,9 @@ dependencies = [
  "actix-rt",
  "actix-service",
  "actix-utils",
- "ahash 0.8.10",
+ "ahash 0.8.11",
  "base64 0.21.7",
- "bitflags 2.4.2",
+ "bitflags 2.5.0",
  "brotli",
  "bytes",
  "bytestring",
@@ -92,7 +92,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb"
 dependencies = [
  "quote",
- "syn 2.0.51",
+ "syn 2.0.55",
 ]
 
 [[package]]
@@ -171,7 +171,7 @@ dependencies = [
  "actix-service",
  "actix-utils",
  "actix-web-codegen",
- "ahash 0.8.10",
+ "ahash 0.8.11",
  "bytes",
  "bytestring",
  "cfg-if",
@@ -205,7 +205,7 @@ dependencies = [
  "actix-router",
  "proc-macro2",
  "quote",
- "syn 2.0.51",
+ "syn 2.0.55",
 ]
 
 [[package]]
@@ -223,6 +223,41 @@ version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
 
+[[package]]
+name = "aead"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
+dependencies = [
+ "crypto-common",
+ "generic-array",
+]
+
+[[package]]
+name = "aes"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+]
+
+[[package]]
+name = "aes-gcm"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
+dependencies = [
+ "aead",
+ "aes",
+ "cipher",
+ "ctr",
+ "ghash",
+ "subtle",
+]
+
 [[package]]
 name = "ahash"
 version = "0.7.8"
@@ -236,9 +271,9 @@ dependencies = [
 
 [[package]]
 name = "ahash"
-version = "0.8.10"
+version = "0.8.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b79b82693f705137f8fb9b37871d99e4f9a7df12b917eed79c3d3954830a60b"
+checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
 dependencies = [
  "cfg-if",
  "const-random",
@@ -250,9 +285,9 @@ dependencies = [
 
 [[package]]
 name = "aho-corasick"
-version = "1.1.2"
+version = "1.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
 dependencies = [
  "memchr",
 ]
@@ -391,9 +426,9 @@ dependencies = [
 
 [[package]]
 name = "anyhow"
-version = "1.0.80"
+version = "1.0.81"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1"
+checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
 
 [[package]]
 name = "assert-json-diff"
@@ -423,7 +458,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3"
 dependencies = [
  "concurrent-queue",
- "event-listener 5.1.0",
+ "event-listener 5.2.0",
  "event-listener-strategy 0.5.0",
  "futures-core",
  "pin-project-lite",
@@ -438,8 +473,8 @@ dependencies = [
  "async-lock 3.3.0",
  "async-task",
  "concurrent-queue",
- "fastrand 2.0.1",
- "futures-lite 2.2.0",
+ "fastrand 2.0.2",
+ "futures-lite 2.3.0",
  "slab",
 ]
 
@@ -451,10 +486,10 @@ checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c"
 dependencies = [
  "async-channel 2.2.0",
  "async-executor",
- "async-io 2.3.1",
+ "async-io 2.3.2",
  "async-lock 3.3.0",
  "blocking",
- "futures-lite 2.2.0",
+ "futures-lite 2.3.0",
  "once_cell",
 ]
 
@@ -491,18 +526,18 @@ dependencies = [
 
 [[package]]
 name = "async-io"
-version = "2.3.1"
+version = "2.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f97ab0c5b00a7cdbe5a371b9a782ee7be1316095885c8a4ea1daf490eb0ef65"
+checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884"
 dependencies = [
  "async-lock 3.3.0",
  "cfg-if",
  "concurrent-queue",
  "futures-io",
- "futures-lite 2.2.0",
+ "futures-lite 2.3.0",
  "parking",
- "polling 3.5.0",
- "rustix 0.38.31",
+ "polling 3.6.0",
+ "rustix 0.38.32",
  "slab",
  "tracing",
  "windows-sys 0.52.0",
@@ -548,13 +583,13 @@ checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799"
 
 [[package]]
 name = "async-trait"
-version = "0.1.77"
+version = "0.1.79"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
+checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.51",
+ "syn 2.0.55",
 ]
 
 [[package]]
@@ -581,27 +616,17 @@ version = "1.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
 
-[[package]]
-name = "atomic-write-file"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edcdbedc2236483ab103a53415653d6b4442ea6141baf1ffa85df29635e88436"
-dependencies = [
- "nix",
- "rand 0.8.5",
-]
-
 [[package]]
 name = "autocfg"
-version = "1.1.0"
+version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
 
 [[package]]
 name = "backtrace"
-version = "0.3.69"
+version = "0.3.71"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
+checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d"
 dependencies = [
  "addr2line",
  "cc",
@@ -624,6 +649,12 @@ version = "0.21.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
 
+[[package]]
+name = "base64"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51"
+
 [[package]]
 name = "bitflags"
 version = "1.3.2"
@@ -632,9 +663,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
 [[package]]
 name = "bitflags"
-version = "2.4.2"
+version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
+checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
 
 [[package]]
 name = "block-buffer"
@@ -663,18 +694,18 @@ dependencies = [
  "async-channel 2.2.0",
  "async-lock 3.3.0",
  "async-task",
- "fastrand 2.0.1",
+ "fastrand 2.0.2",
  "futures-io",
- "futures-lite 2.2.0",
+ "futures-lite 2.3.0",
  "piper",
  "tracing",
 ]
 
 [[package]]
 name = "brotli"
-version = "3.4.0"
+version = "3.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f"
+checksum = "d640d25bc63c50fb1f0b545ffd80207d2e10a4c965530809b40ba3386825c391"
 dependencies = [
  "alloc-no-stdlib",
  "alloc-stdlib",
@@ -693,9 +724,9 @@ dependencies = [
 
 [[package]]
 name = "bumpalo"
-version = "3.15.3"
+version = "3.15.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b"
+checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa"
 
 [[package]]
 name = "bytecount"
@@ -711,9 +742,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
 
 [[package]]
 name = "bytes"
-version = "1.5.0"
+version = "1.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
+checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
 
 [[package]]
 name = "bytestring"
@@ -735,9 +766,9 @@ dependencies = [
 
 [[package]]
 name = "cargo-platform"
-version = "0.1.7"
+version = "0.1.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "694c8807f2ae16faecc43dc17d74b3eb042482789fd0eb64b39a2e04e087053f"
+checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc"
 dependencies = [
  "serde",
 ]
@@ -787,10 +818,11 @@ dependencies = [
 
 [[package]]
 name = "cc"
-version = "1.0.88"
+version = "1.0.90"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02f341c093d19155a6e41631ce5971aac4e9a868262212153124c15fa22d1cdc"
+checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5"
 dependencies = [
+ "jobserver",
  "libc",
 ]
 
@@ -828,9 +860,9 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "4.5.1"
+version = "4.5.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da"
+checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
 dependencies = [
  "clap_builder",
  "clap_derive",
@@ -838,9 +870,9 @@ dependencies = [
 
 [[package]]
 name = "clap_builder"
-version = "4.5.1"
+version = "4.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb"
+checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
 dependencies = [
  "anstream",
  "anstyle",
@@ -850,14 +882,14 @@ dependencies = [
 
 [[package]]
 name = "clap_derive"
-version = "4.5.0"
+version = "4.5.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47"
+checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
 dependencies = [
- "heck",
+ "heck 0.5.0",
  "proc-macro2",
  "quote",
- "syn 2.0.51",
+ "syn 2.0.55",
 ]
 
 [[package]]
@@ -872,6 +904,20 @@ version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
 
+[[package]]
+name = "combine"
+version = "4.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "memchr",
+ "pin-project-lite",
+ "tokio",
+ "tokio-util",
+]
+
 [[package]]
 name = "concurrent-queue"
 version = "2.4.0"
@@ -902,9 +948,9 @@ dependencies = [
 
 [[package]]
 name = "const-random"
-version = "0.1.17"
+version = "0.1.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5aaf16c9c2c612020bcfd042e170f6e32de9b9d75adb5277cdbbd2e2c8c8299a"
+checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
 dependencies = [
  "const-random-macro",
 ]
@@ -939,9 +985,12 @@ dependencies = [
 
 [[package]]
 name = "cookie-factory"
-version = "0.3.2"
+version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b"
+checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2"
+dependencies = [
+ "futures",
+]
 
 [[package]]
 name = "core-foundation"
@@ -994,9 +1043,9 @@ dependencies = [
 
 [[package]]
 name = "crossbeam-channel"
-version = "0.5.11"
+version = "0.5.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b"
+checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95"
 dependencies = [
  "crossbeam-utils",
 ]
@@ -1029,9 +1078,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
 dependencies = [
  "generic-array",
+ "rand_core 0.6.4",
  "typenum",
 ]
 
+[[package]]
+name = "ctr"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
+dependencies = [
+ "cipher",
+]
+
 [[package]]
 name = "darling"
 version = "0.14.4"
@@ -1263,12 +1322,12 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
 
 [[package]]
 name = "docker-compose-types"
-version = "0.7.1"
+version = "0.7.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e366950e7785fe0e404901a2b4e5c09ebd6656767f0c2167e34c5068ce0cc2d"
+checksum = "6d6fdd6fa1c9e8e716f5f73406b868929f468702449621e7397066478b9bf89c"
 dependencies = [
  "derive_builder 0.13.1",
- "indexmap 2.2.3",
+ "indexmap 2.2.6",
  "serde",
  "serde_yaml",
 ]
@@ -1358,9 +1417,9 @@ dependencies = [
 
 [[package]]
 name = "event-listener"
-version = "5.1.0"
+version = "5.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7ad6fd685ce13acd6d9541a30f6db6567a7a24c9ffd4ba2955d29e3f22c8b27"
+checksum = "2b5fb89194fa3cad959b833185b3063ba881dbfc7030680b314250779fb4cc91"
 dependencies = [
  "concurrent-queue",
  "parking",
@@ -1383,7 +1442,7 @@ version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291"
 dependencies = [
- "event-listener 5.1.0",
+ "event-listener 5.2.0",
  "pin-project-lite",
 ]
 
@@ -1407,9 +1466,9 @@ dependencies = [
 
 [[package]]
 name = "fastrand"
-version = "2.0.1"
+version = "2.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
+checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984"
 
 [[package]]
 name = "finl_unicode"
@@ -1571,11 +1630,11 @@ dependencies = [
 
 [[package]]
 name = "futures-lite"
-version = "2.2.0"
+version = "2.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba"
+checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5"
 dependencies = [
- "fastrand 2.0.1",
+ "fastrand 2.0.2",
  "futures-core",
  "futures-io",
  "parking",
@@ -1590,7 +1649,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.51",
+ "syn 2.0.55",
 ]
 
 [[package]]
@@ -1673,6 +1732,16 @@ dependencies = [
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "ghash"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
+dependencies = [
+ "opaque-debug",
+ "polyval",
+]
+
 [[package]]
 name = "gimli"
 version = "0.28.1"
@@ -1687,9 +1756,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
 
 [[package]]
 name = "h2"
-version = "0.3.24"
+version = "0.3.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9"
+checksum = "4fbd2820c5e49886948654ab546d0688ff24530286bdcf8fca3cefb16d4618eb"
 dependencies = [
  "bytes",
  "fnv",
@@ -1697,7 +1766,7 @@ dependencies = [
  "futures-sink",
  "futures-util",
  "http",
- "indexmap 2.2.3",
+ "indexmap 2.2.6",
  "slab",
  "tokio",
  "tokio-util",
@@ -1728,7 +1797,7 @@ version = "0.14.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
 dependencies = [
- "ahash 0.8.10",
+ "ahash 0.8.11",
  "allocator-api2",
 ]
 
@@ -1750,11 +1819,17 @@ dependencies = [
  "unicode-segmentation",
 ]
 
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
 [[package]]
 name = "hermit-abi"
-version = "0.3.8"
+version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "379dada1584ad501b383485dd706b8afb7a70fcbc7f4da7d780638a5a6124a60"
+checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
 
 [[package]]
 name = "hex"
@@ -1791,9 +1866,9 @@ dependencies = [
 
 [[package]]
 name = "http"
-version = "0.2.11"
+version = "0.2.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb"
+checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
 dependencies = [
  "bytes",
  "fnv",
@@ -1932,9 +2007,9 @@ dependencies = [
 
 [[package]]
 name = "indexmap"
-version = "2.2.3"
+version = "2.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177"
+checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
 dependencies = [
  "equivalent",
  "hashbrown 0.14.3",
@@ -1994,15 +2069,24 @@ dependencies = [
 
 [[package]]
 name = "itoa"
-version = "1.0.10"
+version = "1.0.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
+checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+
+[[package]]
+name = "jobserver"
+version = "0.1.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6"
+dependencies = [
+ "libc",
+]
 
 [[package]]
 name = "js-sys"
-version = "0.3.68"
+version = "0.3.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee"
+checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
 dependencies = [
  "wasm-bindgen",
 ]
@@ -2065,7 +2149,7 @@ version = "0.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8"
 dependencies = [
- "bitflags 2.4.2",
+ "bitflags 2.5.0",
  "libc",
  "redox_syscall 0.4.1",
 ]
@@ -2117,9 +2201,9 @@ dependencies = [
 
 [[package]]
 name = "log"
-version = "0.4.20"
+version = "0.4.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
 
 [[package]]
 name = "matchers"
@@ -2184,9 +2268,9 @@ dependencies = [
 
 [[package]]
 name = "mio"
-version = "0.8.10"
+version = "0.8.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
+checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
 dependencies = [
  "libc",
  "log",
@@ -2218,17 +2302,6 @@ dependencies = [
  "tempfile",
 ]
 
-[[package]]
-name = "nix"
-version = "0.27.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
-dependencies = [
- "bitflags 2.4.2",
- "cfg-if",
- "libc",
-]
-
 [[package]]
 name = "nom"
 version = "7.1.3"
@@ -2289,13 +2362,19 @@ version = "1.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
 
+[[package]]
+name = "opaque-debug"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
+
 [[package]]
 name = "openssl"
 version = "0.10.64"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f"
 dependencies = [
- "bitflags 2.4.2",
+ "bitflags 2.5.0",
  "cfg-if",
  "foreign-types",
  "libc",
@@ -2312,7 +2391,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.51",
+ "syn 2.0.55",
 ]
 
 [[package]]
@@ -2440,9 +2519,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
 
 [[package]]
 name = "pest"
-version = "2.7.7"
+version = "2.7.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "219c0dcc30b6a27553f9cc242972b67f75b60eb0db71f0b5462f38b058c41546"
+checksum = "56f8023d0fb78c8e03784ea1c7f3fa36e68a723138990b8d5a47d916b651e7a8"
 dependencies = [
  "memchr",
  "thiserror",
@@ -2451,9 +2530,9 @@ dependencies = [
 
 [[package]]
 name = "pest_derive"
-version = "2.7.7"
+version = "2.7.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22e1288dbd7786462961e69bfd4df7848c1e37e8b74303dbdab82c3a9cdd2809"
+checksum = "b0d24f72393fd16ab6ac5738bc33cdb6a9aa73f8b902e8fe29cf4e67d7dd1026"
 dependencies = [
  "pest",
  "pest_generator",
@@ -2461,22 +2540,22 @@ dependencies = [
 
 [[package]]
 name = "pest_generator"
-version = "2.7.7"
+version = "2.7.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1381c29a877c6d34b8c176e734f35d7f7f5b3adaefe940cb4d1bb7af94678e2e"
+checksum = "fdc17e2a6c7d0a492f0158d7a4bd66cc17280308bbaff78d5bef566dca35ab80"
 dependencies = [
  "pest",
  "pest_meta",
  "proc-macro2",
  "quote",
- "syn 2.0.51",
+ "syn 2.0.55",
 ]
 
 [[package]]
 name = "pest_meta"
-version = "2.7.7"
+version = "2.7.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0934d6907f148c22a3acbda520c7eed243ad7487a30f51f6ce52b58b7077a8a"
+checksum = "934cd7631c050f4674352a6e835d5f6711ffbfb9345c2fc0107155ac495ae293"
 dependencies = [
  "once_cell",
  "pest",
@@ -2490,27 +2569,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
 dependencies = [
  "fixedbitset",
- "indexmap 2.2.3",
+ "indexmap 2.2.6",
 ]
 
 [[package]]
 name = "pin-project"
-version = "1.1.4"
+version = "1.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0"
+checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3"
 dependencies = [
  "pin-project-internal",
 ]
 
 [[package]]
 name = "pin-project-internal"
-version = "1.1.4"
+version = "1.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690"
+checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.51",
+ "syn 2.0.55",
 ]
 
 [[package]]
@@ -2544,7 +2623,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4"
 dependencies = [
  "atomic-waker",
- "fastrand 2.0.1",
+ "fastrand 2.0.2",
  "futures-io",
 ]
 
@@ -2572,18 +2651,31 @@ dependencies = [
 
 [[package]]
 name = "polling"
-version = "3.5.0"
+version = "3.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24f040dee2588b4963afb4e420540439d126f73fdacf4a9c486a96d840bac3c9"
+checksum = "e0c976a60b2d7e99d6f229e414670a9b85d13ac305cc6d1e9c134de58c5aaaf6"
 dependencies = [
  "cfg-if",
  "concurrent-queue",
+ "hermit-abi",
  "pin-project-lite",
- "rustix 0.38.31",
+ "rustix 0.38.32",
  "tracing",
  "windows-sys 0.52.0",
 ]
 
+[[package]]
+name = "polyval"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "opaque-debug",
+ "universal-hash",
+]
+
 [[package]]
 name = "powerfmt"
 version = "0.2.0"
@@ -2622,9 +2714,9 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.78"
+version = "1.0.79"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
+checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
 dependencies = [
  "unicode-ident",
 ]
@@ -2635,7 +2727,7 @@ version = "0.9.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b"
 dependencies = [
- "bitflags 2.4.2",
+ "bitflags 2.5.0",
  "memchr",
  "unicase",
 ]
@@ -2740,6 +2832,27 @@ dependencies = [
  "futures-io",
 ]
 
+[[package]]
+name = "redis"
+version = "0.25.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71d64e978fd98a0e6b105d066ba4889a7301fca65aeac850a877d8797343feeb"
+dependencies = [
+ "async-trait",
+ "bytes",
+ "combine",
+ "futures-util",
+ "itoa",
+ "percent-encoding",
+ "pin-project-lite",
+ "ryu",
+ "sha1_smol",
+ "socket2 0.5.6",
+ "tokio",
+ "tokio-util",
+ "url",
+]
+
 [[package]]
 name = "redox_syscall"
 version = "0.2.16"
@@ -2771,13 +2884,13 @@ dependencies = [
 
 [[package]]
 name = "regex"
-version = "1.10.3"
+version = "1.10.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
+checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
 dependencies = [
  "aho-corasick",
  "memchr",
- "regex-automata 0.4.5",
+ "regex-automata 0.4.6",
  "regex-syntax 0.8.2",
 ]
 
@@ -2792,9 +2905,9 @@ dependencies = [
 
 [[package]]
 name = "regex-automata"
-version = "0.4.5"
+version = "0.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
+checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -2815,9 +2928,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
 
 [[package]]
 name = "reqwest"
-version = "0.11.24"
+version = "0.11.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251"
+checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
 dependencies = [
  "base64 0.21.7",
  "bytes",
@@ -2865,8 +2978,8 @@ version = "1.17.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f6273372244d04a8a4b0bec080ea1e710403e88c5d9d83f9808b2bfa64f0982a"
 dependencies = [
- "ahash 0.8.10",
- "bitflags 2.4.2",
+ "ahash 0.8.11",
+ "bitflags 2.5.0",
  "instant",
  "num-traits",
  "once_cell",
@@ -2885,7 +2998,7 @@ checksum = "9db7f8dc4c9d48183a17ce550574c42995252b82d267eaca3fcd1b979159856c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.51",
+ "syn 2.0.55",
 ]
 
 [[package]]
@@ -2980,11 +3093,11 @@ dependencies = [
 
 [[package]]
 name = "rustix"
-version = "0.38.31"
+version = "0.38.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
+checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89"
 dependencies = [
- "bitflags 2.4.2",
+ "bitflags 2.5.0",
  "errno",
  "libc",
  "linux-raw-sys 0.4.13",
@@ -3147,14 +3260,14 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.51",
+ "syn 2.0.55",
 ]
 
 [[package]]
 name = "serde_json"
-version = "1.0.114"
+version = "1.0.115"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
+checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd"
 dependencies = [
  "itoa",
  "ryu",
@@ -3163,9 +3276,9 @@ dependencies = [
 
 [[package]]
 name = "serde_path_to_error"
-version = "0.1.15"
+version = "0.1.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ebd154a240de39fdebcf5775d2675c204d7c13cf39a4c697be6493c8e734337c"
+checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6"
 dependencies = [
  "itoa",
  "serde",
@@ -3200,7 +3313,7 @@ version = "0.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "70c0e00fab6460447391a1981c21341746bc2d0178a7c46a3bbf667f450ac6e4"
 dependencies = [
- "indexmap 2.2.3",
+ "indexmap 2.2.6",
  "itertools",
  "num-traits",
  "once_cell",
@@ -3225,7 +3338,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "strsim 0.11.0",
- "syn 2.0.51",
+ "syn 2.0.55",
 ]
 
 [[package]]
@@ -3240,11 +3353,11 @@ dependencies = [
 
 [[package]]
 name = "serde_yaml"
-version = "0.9.32"
+version = "0.9.34+deprecated"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fd075d994154d4a774f95b51fb96bdc2832b0ea48425c92546073816cda1f2f"
+checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
 dependencies = [
- "indexmap 2.2.3",
+ "indexmap 2.2.6",
  "itoa",
  "ryu",
  "serde",
@@ -3262,6 +3375,12 @@ dependencies = [
  "digest",
 ]
 
+[[package]]
+name = "sha1_smol"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
+
 [[package]]
 name = "sha2"
 version = "0.10.8"
@@ -3317,9 +3436,9 @@ dependencies = [
 
 [[package]]
 name = "smallvec"
-version = "1.13.1"
+version = "1.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
+checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
 dependencies = [
  "serde",
 ]
@@ -3394,12 +3513,12 @@ dependencies = [
 
 [[package]]
 name = "sqlx"
-version = "0.7.3"
+version = "0.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dba03c279da73694ef99763320dea58b51095dfe87d001b1d4b5fe78ba8763cf"
+checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa"
 dependencies = [
- "sqlx-core 0.7.3",
- "sqlx-macros 0.7.3",
+ "sqlx-core 0.7.4",
+ "sqlx-macros 0.7.4",
  "sqlx-postgres",
 ]
 
@@ -3412,7 +3531,7 @@ dependencies = [
  "async-trait",
  "casbin",
  "dotenv",
- "sqlx 0.7.3",
+ "sqlx 0.7.4",
 ]
 
 [[package]]
@@ -3472,17 +3591,16 @@ dependencies = [
 
 [[package]]
 name = "sqlx-core"
-version = "0.7.3"
+version = "0.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d84b0a3c3739e220d94b3239fd69fb1f74bc36e16643423bd99de3b43c21bfbd"
+checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6"
 dependencies = [
- "ahash 0.8.10",
+ "ahash 0.8.11",
  "atoi 2.0.0",
  "byteorder",
  "bytes",
  "crc",
  "crossbeam-queue",
- "dotenvy",
  "either",
  "event-listener 2.5.3",
  "futures-channel",
@@ -3492,7 +3610,7 @@ dependencies = [
  "futures-util",
  "hashlink",
  "hex",
- "indexmap 2.2.3",
+ "indexmap 2.2.6",
  "log",
  "memchr",
  "native-tls",
@@ -3519,7 +3637,7 @@ checksum = "9966e64ae989e7e575b19d7265cb79d7fc3cbbdf179835cb0d716f294c2049c9"
 dependencies = [
  "dotenvy",
  "either",
- "heck",
+ "heck 0.4.1",
  "hex",
  "once_cell",
  "proc-macro2",
@@ -3535,27 +3653,26 @@ dependencies = [
 
 [[package]]
 name = "sqlx-macros"
-version = "0.7.3"
+version = "0.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89961c00dc4d7dffb7aee214964b065072bff69e36ddb9e2c107541f75e4f2a5"
+checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127"
 dependencies = [
  "proc-macro2",
  "quote",
- "sqlx-core 0.7.3",
+ "sqlx-core 0.7.4",
  "sqlx-macros-core",
  "syn 1.0.109",
 ]
 
 [[package]]
 name = "sqlx-macros-core"
-version = "0.7.3"
+version = "0.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0bd4519486723648186a08785143599760f7cc81c52334a55d6a83ea1e20841"
+checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8"
 dependencies = [
- "atomic-write-file",
  "dotenvy",
  "either",
- "heck",
+ "heck 0.4.1",
  "hex",
  "once_cell",
  "proc-macro2",
@@ -3563,7 +3680,7 @@ dependencies = [
  "serde",
  "serde_json",
  "sha2",
- "sqlx-core 0.7.3",
+ "sqlx-core 0.7.4",
  "sqlx-postgres",
  "syn 1.0.109",
  "tempfile",
@@ -3573,13 +3690,13 @@ dependencies = [
 
 [[package]]
 name = "sqlx-postgres"
-version = "0.7.3"
+version = "0.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d6ac0ac3b7ccd10cc96c7ab29791a7dd236bd94021f31eec7ba3d46a74aa1c24"
+checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e"
 dependencies = [
  "atoi 2.0.0",
  "base64 0.21.7",
- "bitflags 2.4.2",
+ "bitflags 2.5.0",
  "byteorder",
  "crc",
  "dotenvy",
@@ -3600,10 +3717,9 @@ dependencies = [
  "rand 0.8.5",
  "serde",
  "serde_json",
- "sha1",
  "sha2",
  "smallvec",
- "sqlx-core 0.7.3",
+ "sqlx-core 0.7.4",
  "stringprep",
  "thiserror",
  "tracing",
@@ -3629,6 +3745,8 @@ dependencies = [
  "actix-cors",
  "actix-http",
  "actix-web",
+ "aes-gcm",
+ "base64 0.22.0",
  "brotli",
  "chrono",
  "clap",
@@ -3637,13 +3755,14 @@ dependencies = [
  "derive_builder 0.12.0",
  "docker-compose-types",
  "futures",
- "futures-lite 2.2.0",
+ "futures-lite 2.3.0",
  "futures-util",
  "glob",
  "hmac",
- "indexmap 2.2.3",
+ "indexmap 2.2.6",
  "lapin",
  "rand 0.8.5",
+ "redis",
  "regex",
  "reqwest",
  "serde",
@@ -3715,9 +3834,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.51"
+version = "2.0.55"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ab617d94515e94ae53b8406c628598680aa0c9587474ecbe58188f7b345d66c"
+checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -3776,8 +3895,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
 dependencies = [
  "cfg-if",
- "fastrand 2.0.1",
- "rustix 0.38.31",
+ "fastrand 2.0.2",
+ "rustix 0.38.32",
  "windows-sys 0.52.0",
 ]
 
@@ -3792,22 +3911,22 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "1.0.57"
+version = "1.0.58"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
+checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.57"
+version = "1.0.58"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
+checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.51",
+ "syn 2.0.55",
 ]
 
 [[package]]
@@ -3924,7 +4043,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.51",
+ "syn 2.0.55",
 ]
 
 [[package]]
@@ -3950,9 +4069,9 @@ dependencies = [
 
 [[package]]
 name = "tokio-stream"
-version = "0.1.14"
+version = "0.1.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842"
+checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af"
 dependencies = [
  "futures-core",
  "pin-project-lite",
@@ -4002,9 +4121,9 @@ dependencies = [
 
 [[package]]
 name = "tracing-actix-web"
-version = "0.7.9"
+version = "0.7.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fe0d5feac3f4ca21ba33496bcb1ccab58cca6412b1405ae80f0581541e0ca78"
+checksum = "fa069bd1503dd526ee793bb3fce408895136c95fc86d2edb2acf1c646d7f0684"
 dependencies = [
  "actix-web",
  "mutually_exclusive_features",
@@ -4021,7 +4140,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.51",
+ "syn 2.0.55",
 ]
 
 [[package]]
@@ -4030,7 +4149,7 @@ version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b5c266b9ac83dedf0e0385ad78514949e6d89491269e7065bee51d2bb8ec7373"
 dependencies = [
- "ahash 0.8.10",
+ "ahash 0.8.11",
  "gethostname",
  "log",
  "serde",
@@ -4158,11 +4277,21 @@ version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
 
+[[package]]
+name = "universal-hash"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
+dependencies = [
+ "crypto-common",
+ "subtle",
+]
+
 [[package]]
 name = "unsafe-libyaml"
-version = "0.2.10"
+version = "0.2.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b"
+checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
 
 [[package]]
 name = "untrusted"
@@ -4196,9 +4325,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
 
 [[package]]
 name = "uuid"
-version = "1.7.0"
+version = "1.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a"
+checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
 dependencies = [
  "getrandom 0.2.12",
  "serde",
@@ -4230,9 +4359,9 @@ checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690"
 
 [[package]]
 name = "walkdir"
-version = "2.4.0"
+version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
 dependencies = [
  "same-file",
  "winapi-util",
@@ -4265,11 +4394,17 @@ version = "0.11.0+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
+[[package]]
+name = "wasite"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
+
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.91"
+version = "0.2.92"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f"
+checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
 dependencies = [
  "cfg-if",
  "wasm-bindgen-macro",
@@ -4277,24 +4412,24 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.91"
+version = "0.2.92"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b"
+checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
 dependencies = [
  "bumpalo",
  "log",
  "once_cell",
  "proc-macro2",
  "quote",
- "syn 2.0.51",
+ "syn 2.0.55",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-futures"
-version = "0.4.41"
+version = "0.4.42"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97"
+checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0"
 dependencies = [
  "cfg-if",
  "js-sys",
@@ -4304,9 +4439,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.91"
+version = "0.2.92"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed"
+checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -4314,28 +4449,28 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.91"
+version = "0.2.92"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66"
+checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.51",
+ "syn 2.0.55",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.91"
+version = "0.2.92"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838"
+checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
 
 [[package]]
 name = "web-sys"
-version = "0.3.68"
+version = "0.3.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446"
+checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
@@ -4362,11 +4497,12 @@ dependencies = [
 
 [[package]]
 name = "whoami"
-version = "1.4.1"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50"
+checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9"
 dependencies = [
- "wasm-bindgen",
+ "redox_syscall 0.4.1",
+ "wasite",
  "web-sys",
 ]
 
@@ -4407,7 +4543,7 @@ version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
 dependencies = [
- "windows-targets 0.52.3",
+ "windows-targets 0.52.4",
 ]
 
 [[package]]
@@ -4425,7 +4561,7 @@ version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
 dependencies = [
- "windows-targets 0.52.3",
+ "windows-targets 0.52.4",
 ]
 
 [[package]]
@@ -4445,17 +4581,17 @@ dependencies = [
 
 [[package]]
 name = "windows-targets"
-version = "0.52.3"
+version = "0.52.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f"
+checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
 dependencies = [
- "windows_aarch64_gnullvm 0.52.3",
- "windows_aarch64_msvc 0.52.3",
- "windows_i686_gnu 0.52.3",
- "windows_i686_msvc 0.52.3",
- "windows_x86_64_gnu 0.52.3",
- "windows_x86_64_gnullvm 0.52.3",
- "windows_x86_64_msvc 0.52.3",
+ "windows_aarch64_gnullvm 0.52.4",
+ "windows_aarch64_msvc 0.52.4",
+ "windows_i686_gnu 0.52.4",
+ "windows_i686_msvc 0.52.4",
+ "windows_x86_64_gnu 0.52.4",
+ "windows_x86_64_gnullvm 0.52.4",
+ "windows_x86_64_msvc 0.52.4",
 ]
 
 [[package]]
@@ -4466,9 +4602,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
 
 [[package]]
 name = "windows_aarch64_gnullvm"
-version = "0.52.3"
+version = "0.52.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6"
+checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
 
 [[package]]
 name = "windows_aarch64_msvc"
@@ -4478,9 +4614,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
 
 [[package]]
 name = "windows_aarch64_msvc"
-version = "0.52.3"
+version = "0.52.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f"
+checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
 
 [[package]]
 name = "windows_i686_gnu"
@@ -4490,9 +4626,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
 
 [[package]]
 name = "windows_i686_gnu"
-version = "0.52.3"
+version = "0.52.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb"
+checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
 
 [[package]]
 name = "windows_i686_msvc"
@@ -4502,9 +4638,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
 
 [[package]]
 name = "windows_i686_msvc"
-version = "0.52.3"
+version = "0.52.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58"
+checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
 
 [[package]]
 name = "windows_x86_64_gnu"
@@ -4514,9 +4650,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
 
 [[package]]
 name = "windows_x86_64_gnu"
-version = "0.52.3"
+version = "0.52.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614"
+checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
 
 [[package]]
 name = "windows_x86_64_gnullvm"
@@ -4526,9 +4662,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
 
 [[package]]
 name = "windows_x86_64_gnullvm"
-version = "0.52.3"
+version = "0.52.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c"
+checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
 
 [[package]]
 name = "windows_x86_64_msvc"
@@ -4538,9 +4674,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
 
 [[package]]
 name = "windows_x86_64_msvc"
-version = "0.52.3"
+version = "0.52.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6"
+checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
 
 [[package]]
 name = "winreg"
@@ -4606,7 +4742,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.51",
+ "syn 2.0.55",
 ]
 
 [[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 982000f3..030e403f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -58,6 +58,9 @@ deadpool-lapin = "0.11.0"
 docker-compose-types = "0.7.0"
 #actix-casbin-auth = { git = "https://github.com/casbin-rs/actix-casbin-auth.git"}
 actix-casbin-auth = { git = "https://github.com/smart--petea/actix-casbin-auth.git", branch = "dirty-master"}
+aes-gcm = "0.10.3"
+base64 = "0.22.0"
+redis = { version = "0.25.2", features = ["tokio-comp"] }
 
 [dependencies.sqlx]
 version = "0.6.3"
diff --git a/docker-compose.yml b/docker-compose.yml
index eeb10eb2..4ba581c1 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -4,6 +4,9 @@ volumes:
   stackerdb:
     driver: local
 
+  redis-data:
+    driver: local
+
 
 services:
 
@@ -29,6 +32,25 @@ services:
 #      stackerdb:
 #        condition: service_healthy
 
+  redis:
+      container_name: redis
+      image: redis
+      restart: always
+      ports:
+        - 6379:6379
+      volumes:
+        - redis-data:/data
+#        - ./redis/rc.local:/etc/rc.local
+#        - ./redis/redis.conf:/usr/local/etc/redis/redis.conf
+      sysctls:
+        net.core.somaxconn: 1024
+      logging:
+        driver: "json-file"
+        options:
+          max-size: "10m"
+          tag: "container_{{.Name}}"
+
+
 #  stacker_queue:
 #    image: trydirect/stacker:0.0.7
 #    container_name: stacker_queue
diff --git a/docker/dev/.env b/docker/dev/.env
index 6371a972..d60f2662 100644
--- a/docker/dev/.env
+++ b/docker/dev/.env
@@ -1,5 +1,8 @@
+SECURITY_KEY=SECURITY_KEY_SHOULD_BE_OF_LEN_32
+
 DATABASE_URL=postgres://postgres:postgres@stackerdb:5432/stacker
 POSTGRES_USER=postgres
 POSTGRES_PASSWORD=postgres
 POSTGRES_DB=stacker
-POSTGRES_PORT=5432
\ No newline at end of file
+POSTGRES_PORT=5432
+
diff --git a/docker/dev/docker-compose.yml b/docker/dev/docker-compose.yml
index 3577b145..eeac0386 100644
--- a/docker/dev/docker-compose.yml
+++ b/docker/dev/docker-compose.yml
@@ -4,6 +4,9 @@ volumes:
   stackerdb:
     driver: local
 
+  redis-data:
+    driver: local
+
 networks:
   backend:
     driver: bridge
@@ -75,3 +78,23 @@ services:
       - ./postgresql.conf:/etc/postgresql/postgresql.conf
     networks:
       - backend
+
+  redis:
+    container_name: stackerredis
+    image: redis:latest
+    restart: always
+    ports:
+      - 6379
+    volumes:
+      - redis-data:/data
+    #        - ./redis/rc.local:/etc/rc.local
+    #        - ./redis/redis.conf:/usr/local/etc/redis/redis.conf
+    sysctls:
+      net.core.somaxconn: 1024
+    logging:
+      driver: "json-file"
+      options:
+        max-size: "10m"
+        tag: "container_{{.Name}}"
+
+
diff --git a/src/forms/cloud.rs b/src/forms/cloud.rs
index ab040c37..8cc1567e 100644
--- a/src/forms/cloud.rs
+++ b/src/forms/cloud.rs
@@ -1,32 +1,146 @@
 use crate::models;
-use serde::{Deserialize, Serialize};
+use serde::{Deserialize, Serialize, Serializer};
 use serde_valid::Validate;
+use crate::helpers::cloud::security::Secret;
+use tracing::Instrument;
 
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+
+// fn hide_part<S>(value: &Option<String>, s: S) -> Result<S::Ok, S::Error>
+//     where
+//         S: Serializer,
+// {
+//     eprintln!("value in serde {:?}", value);
+//     let result: &str = match value {
+//         Some(value) => {
+//             let value = value.as_str();
+//             value
+//             // value.into_iter().take(6).collect::<String>()
+//         }
+//         None => "",
+//     };
+//     s.serialize_str(result)
+// }
+fn hide_parts(value: String) -> String {
+    value.chars().into_iter().take(6).collect::<String>() + "****"
+}
+
+
+#[derive(Default, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Cloud {
+    pub user_id: Option<String>,
     pub project_id: Option<i32>,
     #[validate(min_length = 2)]
     #[validate(max_length = 50)]
     pub provider: String,
+    // #[serde(serialize_with = "hide_part")]
     pub cloud_token: Option<String>,
+    // #[serde(serialize_with = "hide_part")]
     pub cloud_key: Option<String>,
+    // #[serde(serialize_with = "hide_part")]
     pub cloud_secret: Option<String>,
     pub save_token: Option<bool>,
 }
 
+impl Cloud {
+    pub(crate) fn decode(secret: &mut Secret, encrypted_value: String) -> String {
+        // tracing::error!("encrypted_value {:?}", &encrypted_value);
+        let b64_decoded = Secret::b64_decode(&encrypted_value).unwrap();
+        match secret.decrypt(b64_decoded) {
+            Ok(decoded) => decoded,
+            Err(_err) => {
+                tracing::error!("🟥 Could not decode {:?},{:?}",secret.field,_err);
+                // panic!("Could not decode ");
+                "".to_owned()
+            }
+        }
+    }
+
+    // @todo should be refactored, may be moved to cloud.into() or Secret::from()
+    pub(crate) fn decode_model(mut cloud: models::Cloud) -> models::Cloud {
+
+        let encrypted_value = cloud.cloud_token.clone().unwrap();
+        let mut secret = Secret::new();
+        secret.user_id = cloud.user_id.clone();
+        secret.project_id = cloud.project_id.clone().unwrap();
+        secret.field = "cloud_token".to_string();
+        let cloud_token = Cloud::decode(&mut secret, encrypted_value);
+        cloud.cloud_token = Some(hide_parts(cloud_token));
+        cloud
+    }
+}
+
+impl std::fmt::Debug for Cloud {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let cloud_key: String = match self.cloud_key.as_ref() {
+            Some(val) =>
+                {
+                    val.chars().take(4).collect::<String>() + "****"
+                },
+            None => "".to_string(),
+        };
+        let cloud_token: String = match self.cloud_token.as_ref() {
+            Some(val) => {
+                eprintln!("cloud token {val:?}");
+                val.chars().take(4).collect::<String>() + "****"
+            },
+            None => "".to_string(),
+        };
+
+        let cloud_secret: String = match self.cloud_secret.as_ref() {
+            Some(val) => {
+                val.chars().take(4).collect::<String>() + "****"
+            }
+            None => "".to_string(),
+        };
+
+        write!(f, "{} cloud creds: cloud_key : {} cloud_token: {} cloud_secret: {} project_id: {:?}",
+               self.provider,
+               cloud_key,
+               cloud_token,
+               cloud_secret,
+               self.project_id
+        )
+    }
+}
+
 impl Into<models::Cloud> for &Cloud {
     fn into(self) -> models::Cloud {
         let mut cloud = models::Cloud::default();
         cloud.provider = self.provider.clone();
-        cloud.cloud_token = self.cloud_token.clone();
-        cloud.cloud_key = self.cloud_key.clone();
-        cloud.cloud_secret = self.cloud_secret.clone();
+        cloud.user_id = self.user_id.clone().unwrap();
+        cloud.project_id = self.project_id;
+
+        let mut secret = Secret::new();
+        secret.user_id = self.user_id.clone().unwrap();
+        secret.project_id = self.project_id.unwrap();
+
+        if let Some(val) = self.cloud_token.clone() {
+            secret.field = "cloud_token".to_owned();
+            if let Ok(encrypted) = secret.encrypt(val) {
+                cloud.cloud_token = Some(Secret::b64_encode(&encrypted));
+            };
+        }
+        if let Some(val) = self.cloud_key.clone() {
+            secret.field = "cloud_key".to_owned();
+            if let Ok(encrypted) = secret.encrypt(val) {
+                // cloud.cloud_token = Some(encrypted.data.iter().map(|&c| c as char).collect::<String>())
+                cloud.cloud_key = Some(Secret::b64_encode(&encrypted));
+            };
+        }
+        if let Some(val) = self.cloud_secret.clone() {
+            secret.field = "cloud_secret".to_owned();
+            if let Ok(encrypted) = secret.encrypt(val) {
+                cloud.cloud_secret = Some(Secret::b64_encode(&encrypted))
+            };
+        }
         cloud.save_token = self.save_token.clone();
 
         cloud
     }
+
 }
 
+
 impl Into<Cloud> for models::Cloud {
     fn into(self) -> Cloud {
         let mut form = Cloud::default();
diff --git a/src/helpers/cloud/mod.rs b/src/helpers/cloud/mod.rs
new file mode 100644
index 00000000..9d2c9991
--- /dev/null
+++ b/src/helpers/cloud/mod.rs
@@ -0,0 +1,2 @@
+pub(crate) mod security;
+pub use security::*;
\ No newline at end of file
diff --git a/src/helpers/cloud/security.rs b/src/helpers/cloud/security.rs
new file mode 100644
index 00000000..29b997cf
--- /dev/null
+++ b/src/helpers/cloud/security.rs
@@ -0,0 +1,135 @@
+use aes_gcm::{
+    aead::{Aead, AeadCore, KeyInit, OsRng},
+    Aes256Gcm, Nonce, Key // Or `Aes128Gcm`
+};
+use base64::{engine::general_purpose, Engine as _};
+use redis::{Commands, Connection};
+use tracing::Instrument;
+
+#[derive(Debug, Default, PartialEq, Clone)]
+pub(crate) struct Secret {
+    pub(crate) user_id: String,
+    pub(crate) project_id: i32,
+    pub(crate) field: String, // cloud_token/cloud_key/cloud_secret
+    pub(crate) nonce: Vec<u8>,
+}
+
+
+impl Secret {
+    pub fn new() -> Self {
+
+        Secret {
+            user_id: "".to_string(),
+            project_id: 0,
+            field: "".to_string(),
+            nonce: vec![],
+        }
+    }
+    #[tracing::instrument(name = "Secret::connect_storage")]
+    fn connect_storage() -> Connection {
+        match redis::Client::open("redis://127.0.0.1/"){
+            Ok(client) => {
+                match client.get_connection() {
+                    Ok(connection) => connection,
+                    Err(_err) => panic!("Error connecting Redis")
+                }
+            }
+            Err(err) => panic!("Could not connect to Redis, {:?}", err)
+        }
+    }
+
+    #[tracing::instrument(name = "Secret::save")]
+    fn save(&self, value: &[u8]) -> &Self {
+        let mut conn = Secret::connect_storage();
+        let key = format!("{}_{}_{}", self.user_id, self.project_id, self.field);
+        tracing::debug!("Saving into storage..");
+        let _: () = match conn.set(key, value) {
+            Ok(s) => s,
+            Err(e) => panic!("Could not save to storage {}", e)
+        };
+        self
+    }
+
+    pub fn b64_encode(value: &Vec<u8>) -> String {
+        general_purpose::STANDARD.encode(value)
+    }
+
+    pub fn b64_decode(value: &String) -> Result<Vec<u8>, String> {
+        general_purpose::STANDARD.decode(value)
+            .map_err(|e| format!("b64_decode error {}", e))
+    }
+
+    #[tracing::instrument(name = "Secret::get")]
+    fn get(&mut self, key: String) -> &mut Self {
+        let mut conn = Secret::connect_storage();
+        let nonce: Vec<u8> = match conn.get(&key) {
+            Ok(value) => {
+                tracing::debug!("Got value from storage {:?}", &value);
+                value
+            },
+            Err(_e) => {
+                tracing::error!("Could not get value from storage by key {:?} {:?}", &key,  _e);
+                vec![]
+            }
+        };
+
+        self.nonce = nonce;
+        self
+    }
+
+    #[tracing::instrument(name = "encrypt.")]
+    pub fn encrypt(&self, token: String) -> Result<Vec<u8>, String> {
+
+        // let sec_key = std::env::var("SECURITY_KEY")
+        //     .expect("SECURITY_KEY environment variable is not set")
+        //     .as_bytes();
+        let sec_key = "SECURITY_KEY_SHOULD_BE_OF_LEN_32".as_bytes();
+        // let key = Aes256Gcm::generate_key(OsRng);
+        let key: &Key::<Aes256Gcm> = Key::<Aes256Gcm>::from_slice(&sec_key);
+        // eprintln!("encrypt key {key:?}");
+        // eprintln!("encrypt: from slice key {key:?}");
+        let cipher = Aes256Gcm::new(&key);
+        // eprintln!("encrypt: Cipher str {cipher:?}");
+        let nonce = Aes256Gcm::generate_nonce(&mut OsRng); // 96-bits; unique per message
+        eprintln!("Nonce bytes {nonce:?}");
+        // let nonce_b64: String = general_purpose::STANDARD.encode(nonce);
+        // eprintln!("Nonce b64 {nonce_b64:?}");
+        eprintln!("token {token:?}");
+
+        let cipher_vec = cipher.encrypt(&nonce, token.as_ref())
+            .map_err(|e| format!("{:?}", e))?;
+
+        // store nonce for a limited amount of time
+        // self.save(cipher_vec.clone());
+        self.save(nonce.as_slice());
+
+        eprintln!("Cipher {cipher_vec:?}");
+        Ok(cipher_vec)
+    }
+
+    #[tracing::instrument(name = "decrypt.")]
+    pub fn decrypt(&mut self, encrypted_data: Vec<u8>) -> Result<String, String> {
+        let sec_key = "SECURITY_KEY_SHOULD_BE_OF_LEN_32".as_bytes();
+        // let sec_key = std::env::var("SECURITY_KEY")
+        //     .expect("SECURITY_KEY environment variable is not set")
+        //     .as_bytes();
+        let key: &Key::<Aes256Gcm> = Key::<Aes256Gcm>::from_slice(&sec_key);
+        // eprintln!("decrypt: Key str {key:?}");
+        let rkey = format!("{}_{}_{}", self.user_id, self.project_id, self.field);
+        eprintln!("decrypt: Key str {rkey:?}");
+        self.get(rkey);
+        // eprintln!("decrypt: nonce b64:decoded {nonce:?}");
+
+        let nonce = Nonce::from_slice(self.nonce.as_slice());
+        eprintln!("decrypt: nonce {nonce:?}");
+
+        let cipher = Aes256Gcm::new(&key);
+        // eprintln!("decrypt: Cipher str {cipher:?}");
+        eprintln!("decrypt: str {encrypted_data:?}");
+
+        let plaintext = cipher.decrypt(&nonce, encrypted_data.as_ref())
+            .map_err(|e| format!("{:?}", e))?;
+
+        Ok(String::from_utf8(plaintext).map_err(|e| format!("{:?}", e))?)
+    }
+}
\ No newline at end of file
diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs
index 0640df84..368eafd6 100644
--- a/src/helpers/mod.rs
+++ b/src/helpers/mod.rs
@@ -7,5 +7,8 @@ pub use json::*;
 pub use mq_manager::*;
 pub mod dockerhub;
 pub(crate) mod compressor;
+pub(crate) mod cloud;
 
 pub use dockerhub::*;
+
+pub use cloud::*;
\ No newline at end of file
diff --git a/src/middleware/authentication/method/f_oauth.rs b/src/middleware/authentication/method/f_oauth.rs
index efe215c6..8400e81d 100644
--- a/src/middleware/authentication/method/f_oauth.rs
+++ b/src/middleware/authentication/method/f_oauth.rs
@@ -20,7 +20,7 @@ fn try_extract_token(authentication: String) -> Result<String, String> {
     Ok(token.unwrap().into())
 }
 
-#[tracing::instrument(name = "try authenticate via bearer")]
+#[tracing::instrument(name = "Authenticate with bearer token")]
 pub async fn try_oauth(req: &mut ServiceRequest) -> Result<bool, String> {
     let authentication = get_header::<String>(&req, "authorization")?;
     if authentication.is_none() {
@@ -33,7 +33,8 @@ pub async fn try_oauth(req: &mut ServiceRequest) -> Result<bool, String> {
         .await
         .map_err(|err| format!("{err}"))?;
 
-    let accesscontrol_vals = actix_casbin_auth::CasbinVals {
+    // println!("user ================== {}", user.id.clone());
+    let acl_vals = actix_casbin_auth::CasbinVals {
         subject: user.id.clone(),
         domain: None,
     };
@@ -42,8 +43,8 @@ pub async fn try_oauth(req: &mut ServiceRequest) -> Result<bool, String> {
         return Err("user already logged".to_string());
     }
 
-    if req.extensions_mut().insert(accesscontrol_vals).is_some() {
-        return Err("sth wrong with access control".to_string());
+    if req.extensions_mut().insert(acl_vals).is_some() {
+        return Err("Something wrong with access control".to_string());
     }
 
     Ok(true)
@@ -58,7 +59,7 @@ async fn fetch_user(auth_url: &str, token: &str) -> Result<models::User, String>
         .header(ACCEPT, "application/json")
         .send()
         .await
-        .map_err(|_err| "no resp from auth server".to_string())?;
+        .map_err(|_err| "No response from OAuth server".to_string())?;
 
     if !resp.status().is_success() {
         return Err("401 Unauthorized".to_string());
diff --git a/src/models/cloud.rs b/src/models/cloud.rs
index 91b494c1..d0fd31bd 100644
--- a/src/models/cloud.rs
+++ b/src/models/cloud.rs
@@ -14,3 +14,38 @@ pub struct Cloud {
     pub created_at: DateTime<Utc>,
     pub updated_at: DateTime<Utc>,
 }
+
+
+
+impl std::fmt::Display for Cloud {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let cloud_key: String = match self.cloud_key.as_ref() {
+            Some(val) =>
+                {
+                    val.chars().take(4).collect::<String>() + "****"
+                },
+            None => "".to_string(),
+        };
+        let cloud_token: String = match self.cloud_token.as_ref() {
+            Some(val) => {
+                val.chars().take(4).collect::<String>() + "****"
+            },
+            None => "".to_string(),
+        };
+
+        let cloud_secret: String = match self.cloud_secret.as_ref() {
+            Some(val) => {
+                val.chars().take(4).collect::<String>() + "****"
+            }
+            None => "".to_string(),
+        };
+
+        write!(f, "{} cloud creds: cloud_key : {} cloud_token: {} cloud_secret: {} project_id: {:?}",
+               self.provider,
+               cloud_key,
+               cloud_token,
+               cloud_secret,
+               self.project_id
+        )
+    }
+}
diff --git a/src/routes/cloud/add.rs b/src/routes/cloud/add.rs
index ca8e7b80..ce4126fa 100644
--- a/src/routes/cloud/add.rs
+++ b/src/routes/cloud/add.rs
@@ -6,18 +6,16 @@ use crate::db;
 use actix_web::{post, web, Responder, Result};
 use sqlx::PgPool;
 use std::sync::Arc;
+use chrono::Utc;
 use serde_valid::Validate;
+use tracing::Instrument;
 
-// workflow
-// add, update, list, get(user_id), ACL,
-// ACL - access to func for a user
-// ACL - access to objects for a user
 
 #[tracing::instrument(name = "Add cloud.")]
 #[post("")]
 pub async fn add(
     user: web::ReqData<Arc<models::User>>,
-    form: web::Json<forms::cloud::Cloud>,
+    mut form: web::Json<forms::cloud::Cloud>,
     pg_pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
 
@@ -29,8 +27,10 @@ pub async fn add(
         return Err(JsonResponse::<models::Project>::build().form_error(errors));
     }
 
+    form.user_id = Some(user.id.clone());
     let mut cloud: models::Cloud = form.deref().into();
-    cloud.user_id = user.id.clone();
+    cloud.created_at = Utc::now();
+    cloud.updated_at = Utc::now();
 
     db::cloud::insert(pg_pool.get_ref(), cloud)
         .await
diff --git a/src/routes/cloud/get.rs b/src/routes/cloud/get.rs
index 23af867c..82983985 100644
--- a/src/routes/cloud/get.rs
+++ b/src/routes/cloud/get.rs
@@ -4,31 +4,34 @@ use crate::helpers::JsonResponse;
 use crate::models;
 use actix_web::{get, web, Responder, Result};
 use sqlx::PgPool;
+use crate::forms::Cloud;
+use tracing::Instrument;
 
-// workflow
-// add, update, list, get(user_id), ACL,
-// ACL - access to func for a user
-// ACL - access to objects for a user
-
-#[tracing::instrument(name = "Get cloud.")]
+#[tracing::instrument(name = "Get cloud credentials.")]
 #[get("/{id}")]
 pub async fn item(
     path: web::Path<(i32,)>,
+    user: web::ReqData<Arc<models::User>>,
     pg_pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
     let id = path.0;
-    let cloud = db::cloud::fetch(pg_pool.get_ref(), id)
+    db::cloud::fetch(pg_pool.get_ref(), id)
         .await
         .map_err(|_err| JsonResponse::<models::Cloud>::build()
             .internal_server_error(""))
         .and_then(|cloud| {
             match cloud {
-                Some(cloud) => { Ok(cloud) },
-                None => Err(JsonResponse::<models::Cloud>::build().not_found("object not found"))
+                Some(cloud) if cloud.user_id != user.id => {
+                    Err(JsonResponse::not_found("record not found"))
+                },
+                Some(cloud) => {
+                    let cloud = Cloud::decode_model(cloud);
+                    Ok(JsonResponse::build().set_item(Some(cloud)).ok("OK"))
+                },
+                None => Err(JsonResponse::not_found("record not found")),
             }
-        })?;
+        })
 
-    Ok(JsonResponse::build().set_item(cloud).ok("OK"))
 }
 
 #[tracing::instrument(name = "Get all clouds.")]
diff --git a/src/routes/cloud/update.rs b/src/routes/cloud/update.rs
index aca859c3..3c603603 100644
--- a/src/routes/cloud/update.rs
+++ b/src/routes/cloud/update.rs
@@ -8,6 +8,7 @@ use sqlx::PgPool;
 use std::sync::Arc;
 use tracing::Instrument;
 use std::ops::Deref;
+use chrono::Utc;
 
 #[tracing::instrument(name = "Update cloud.")]
 #[put("/{id}")]
@@ -37,6 +38,7 @@ pub async fn item(
     let mut cloud:models::Cloud = form.deref().into();
     cloud.id = cloud_row.id;
     cloud.user_id = user.id.clone();
+    // cloud.updated_at = Utc::now();
 
     tracing::debug!("Updating cloud {:?}", cloud);
 
diff --git a/src/routes/project/compose.rs b/src/routes/project/compose.rs
index de52ca03..ca2e414d 100644
--- a/src/routes/project/compose.rs
+++ b/src/routes/project/compose.rs
@@ -7,7 +7,7 @@ use sqlx::PgPool;
 use std::sync::Arc;
 
 #[tracing::instrument(name = "User's generate docker-compose.")]
-#[get("/{id}")]
+#[get("/{id}/compose")]
 pub async fn add(
     user: web::ReqData<Arc<models::User>>,
     path: web::Path<(i32,)>,
diff --git a/src/routes/server/get.rs b/src/routes/server/get.rs
index de33e8f8..3bd5a6f8 100644
--- a/src/routes/server/get.rs
+++ b/src/routes/server/get.rs
@@ -15,21 +15,24 @@ use sqlx::PgPool;
 #[get("/{id}")]
 pub async fn item(
     path: web::Path<(i32,)>,
+    user: web::ReqData<Arc<models::User>>,
     pg_pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
     let id = path.0;
-    let server = db::server::fetch(pg_pool.get_ref(), id)
+    db::server::fetch(pg_pool.get_ref(), id)
         .await
         .map_err(|_err| JsonResponse::<models::Server>::build()
             .internal_server_error(""))
         .and_then(|server| {
             match server {
-                Some(server) => { Ok(server) },
-                None => Err(JsonResponse::<models::Server>::build().not_found("object not found"))
+                Some(project) if project.user_id != user.id => {
+                    Err(JsonResponse::not_found("not found"))
+                },
+                Some(server) => Ok(JsonResponse::build().set_item(Some(server)).ok("OK")),
+                None => Err(JsonResponse::not_found("not found")),
             }
-        })?;
+        })
 
-    Ok(JsonResponse::build().set_item(server).ok("OK"))
 }
 
 #[tracing::instrument(name = "Get all servers.")]
diff --git a/src/routes/server/update.rs b/src/routes/server/update.rs
index e72c2965..b0741259 100644
--- a/src/routes/server/update.rs
+++ b/src/routes/server/update.rs
@@ -38,8 +38,6 @@ pub async fn item(
     server.id = server_row.id;
     server.project_id = server_row.project_id;
     server.user_id = user.id.clone();
-    // exclude
-    // server.created_at
 
     tracing::debug!("Updating server {:?}", server);
 
diff --git a/src/startup.rs b/src/startup.rs
index e199afab..6fa0f6cb 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -17,6 +17,7 @@ use crate::middleware;
 use sqlx::{Pool, Postgres};
 use std::net::TcpListener;
 use tracing_actix_web::TracingLogger;
+use redis::Commands;
 
 pub async fn run(
     listener: TcpListener,
@@ -41,7 +42,7 @@ pub async fn run(
     let server = HttpServer::new(move || {
         App::new()
             .wrap(TracingLogger::default())
-            .wrap(authorization.clone()) 
+            // .wrap(authorization.clone())
             .wrap(middleware::authentication::Manager::new())
             .wrap(Cors::permissive())
             .service(
@@ -69,7 +70,6 @@ pub async fn run(
                     .service(crate::routes::project::deploy::item)
                     .service(crate::routes::project::deploy::saved_item)
                     .service(crate::routes::project::compose::add)
-                    .service(crate::routes::project::compose::admin)
                     .service(crate::routes::project::get::item)
                     .service(crate::routes::project::add::item)
                     .service(crate::routes::project::update::item) 
@@ -80,6 +80,7 @@ pub async fn run(
                     .service(
                         web::scope("/project")
                             .service(crate::routes::project::get::admin_list)
+                            .service(crate::routes::project::compose::admin)
                     )
                     .service(
                         web::scope("/client")
diff --git a/tests/mock_data/cloud.json b/tests/mock_data/cloud.json
index 04f74125..3865cd7e 100644
--- a/tests/mock_data/cloud.json
+++ b/tests/mock_data/cloud.json
@@ -1,9 +1,8 @@
 {
-  "user_id": "hy181TZa4DaabUZWklsrxw",
   "project_id": 1,
   "provider": "htz",
   "cloud_token": "cloud_token_here",
-  "cloud_key": "cloud_token_here",
+  "cloud_key": "cloud_key_here",
   "cloud_secret": "cloud_secret_here",
   "save_token": true
 }

From ac8ebd5ac04912c00e079c0f9af1e8e81beedac0 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Wed, 27 Mar 2024 13:02:48 +0200
Subject: [PATCH 236/284] secure all other cloud cred fields

---
 src/forms/cloud.rs      | 49 ++++++++++++++++++++++++++++++++++++-----
 src/routes/cloud/get.rs |  2 +-
 2 files changed, 45 insertions(+), 6 deletions(-)

diff --git a/src/forms/cloud.rs b/src/forms/cloud.rs
index 8cc1567e..79515c50 100644
--- a/src/forms/cloud.rs
+++ b/src/forms/cloud.rs
@@ -56,15 +56,39 @@ impl Cloud {
     }
 
     // @todo should be refactored, may be moved to cloud.into() or Secret::from()
-    pub(crate) fn decode_model(mut cloud: models::Cloud) -> models::Cloud {
+    pub(crate) fn decode_model(mut cloud: models::Cloud, reveal:bool) -> models::Cloud {
 
         let encrypted_value = cloud.cloud_token.clone().unwrap();
         let mut secret = Secret::new();
         secret.user_id = cloud.user_id.clone();
         secret.project_id = cloud.project_id.clone().unwrap();
+
         secret.field = "cloud_token".to_string();
         let cloud_token = Cloud::decode(&mut secret, encrypted_value);
-        cloud.cloud_token = Some(hide_parts(cloud_token));
+        if reveal {
+            cloud.cloud_token = Some(cloud_token);
+        } else {
+            cloud.cloud_token = Some(hide_parts(cloud_token));
+        }
+
+        secret.field = "cloud_secret".to_string();
+        let encrypted_value = cloud.cloud_secret.clone().unwrap();
+        let cloud_secret = Cloud::decode(&mut secret, encrypted_value);
+        if reveal {
+            cloud.cloud_secret = Some(cloud_secret);
+        } else {
+            cloud.cloud_secret = Some(hide_parts(cloud_secret));
+        }
+
+        secret.field = "cloud_key".to_string();
+        let encrypted_value = cloud.cloud_key.clone().unwrap();
+        let cloud_key = Cloud::decode(&mut secret, encrypted_value);
+        if reveal {
+            cloud.cloud_key = Some(cloud_key);
+        } else {
+            cloud.cloud_key = Some(hide_parts(cloud_key));
+        }
+
         cloud
     }
 }
@@ -141,14 +165,29 @@ impl Into<models::Cloud> for &Cloud {
 }
 
 
+// on deploy
 impl Into<Cloud> for models::Cloud {
     fn into(self) -> Cloud {
         let mut form = Cloud::default();
         form.project_id = self.project_id;
         form.provider = self.provider;
-        form.cloud_token = self.cloud_token;
-        form.cloud_key = self.cloud_key;
-        form.cloud_secret = self.cloud_secret;
+
+        let mut secret = Secret::new();
+        secret.user_id = self.user_id.clone();
+        secret.project_id = self.project_id.clone().unwrap();
+
+        secret.field = "cloud_token".to_string();
+        let cloud_token = Cloud::decode(&mut secret, self.cloud_token.unwrap());
+        form.cloud_token = Some(cloud_token);
+
+        secret.field = "cloud_key".to_string();
+        let cloud_key = Cloud::decode(&mut secret, self.cloud_key.unwrap());
+        form.cloud_token = Some(cloud_key);
+
+        secret.field = "cloud_secret".to_string();
+        let cloud_secret = Cloud::decode(&mut secret, self.cloud_secret.unwrap());
+        form.cloud_secret = Some(cloud_secret);
+
         form.save_token = self.save_token;
 
         form
diff --git a/src/routes/cloud/get.rs b/src/routes/cloud/get.rs
index 82983985..5f0e7f9b 100644
--- a/src/routes/cloud/get.rs
+++ b/src/routes/cloud/get.rs
@@ -25,7 +25,7 @@ pub async fn item(
                     Err(JsonResponse::not_found("record not found"))
                 },
                 Some(cloud) => {
-                    let cloud = Cloud::decode_model(cloud);
+                    let cloud = Cloud::decode_model(cloud, false);
                     Ok(JsonResponse::build().set_item(Some(cloud)).ok("OK"))
                 },
                 None => Err(JsonResponse::not_found("record not found")),

From 298bd0aa89c7af196471ce0a3a5c4e18abe632f7 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Wed, 27 Mar 2024 13:06:52 +0200
Subject: [PATCH 237/284] little of optimization, get rid of duplcates

---
 src/models/cloud.rs | 30 +++++++++---------------------
 1 file changed, 9 insertions(+), 21 deletions(-)

diff --git a/src/models/cloud.rs b/src/models/cloud.rs
index d0fd31bd..3c2a7521 100644
--- a/src/models/cloud.rs
+++ b/src/models/cloud.rs
@@ -15,30 +15,18 @@ pub struct Cloud {
     pub updated_at: DateTime<Utc>,
 }
 
-
+fn mask_string(s: Option<&String>) -> String {
+    match s {
+        Some(val) => val.chars().take(4).collect::<String>() + "****",
+        None => "".to_string(),
+    }
+}
 
 impl std::fmt::Display for Cloud {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        let cloud_key: String = match self.cloud_key.as_ref() {
-            Some(val) =>
-                {
-                    val.chars().take(4).collect::<String>() + "****"
-                },
-            None => "".to_string(),
-        };
-        let cloud_token: String = match self.cloud_token.as_ref() {
-            Some(val) => {
-                val.chars().take(4).collect::<String>() + "****"
-            },
-            None => "".to_string(),
-        };
-
-        let cloud_secret: String = match self.cloud_secret.as_ref() {
-            Some(val) => {
-                val.chars().take(4).collect::<String>() + "****"
-            }
-            None => "".to_string(),
-        };
+        let cloud_key = mask_string(self.cloud_key.as_ref());
+        let cloud_token = mask_string(self.cloud_token.as_ref());
+        let cloud_secret = mask_string(self.cloud_secret.as_ref());
 
         write!(f, "{} cloud creds: cloud_key : {} cloud_token: {} cloud_secret: {} project_id: {:?}",
                self.provider,

From 9246620419795615b0d9013ff2ce3d301db6b653 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Wed, 27 Mar 2024 13:13:14 +0200
Subject: [PATCH 238/284] reduce repetition and improving readability

---
 src/forms/cloud.rs | 36 +++++++++++++++++-------------------
 1 file changed, 17 insertions(+), 19 deletions(-)

diff --git a/src/forms/cloud.rs b/src/forms/cloud.rs
index 79515c50..debf9758 100644
--- a/src/forms/cloud.rs
+++ b/src/forms/cloud.rs
@@ -127,6 +127,20 @@ impl std::fmt::Debug for Cloud {
     }
 }
 
+fn encrypt_field(
+    secret: &mut Secret,
+    field_name: &str,
+    value: Option<String>,
+) -> Option<String> {
+    if let Some(val) = value {
+        secret.field = field_name.to_owned();
+        if let Ok(encrypted) = secret.encrypt(val) {
+            return Some(Secret::b64_encode(&encrypted));
+        }
+    }
+    None
+}
+
 impl Into<models::Cloud> for &Cloud {
     fn into(self) -> models::Cloud {
         let mut cloud = models::Cloud::default();
@@ -138,25 +152,9 @@ impl Into<models::Cloud> for &Cloud {
         secret.user_id = self.user_id.clone().unwrap();
         secret.project_id = self.project_id.unwrap();
 
-        if let Some(val) = self.cloud_token.clone() {
-            secret.field = "cloud_token".to_owned();
-            if let Ok(encrypted) = secret.encrypt(val) {
-                cloud.cloud_token = Some(Secret::b64_encode(&encrypted));
-            };
-        }
-        if let Some(val) = self.cloud_key.clone() {
-            secret.field = "cloud_key".to_owned();
-            if let Ok(encrypted) = secret.encrypt(val) {
-                // cloud.cloud_token = Some(encrypted.data.iter().map(|&c| c as char).collect::<String>())
-                cloud.cloud_key = Some(Secret::b64_encode(&encrypted));
-            };
-        }
-        if let Some(val) = self.cloud_secret.clone() {
-            secret.field = "cloud_secret".to_owned();
-            if let Ok(encrypted) = secret.encrypt(val) {
-                cloud.cloud_secret = Some(Secret::b64_encode(&encrypted))
-            };
-        }
+        cloud.cloud_token = encrypt_field(&mut secret, "cloud_token", self.cloud_token.clone());
+        cloud.cloud_key = encrypt_field(&mut secret, "cloud_key", self.cloud_key.clone());
+        cloud.cloud_secret = encrypt_field(&mut secret, "cloud_secret", self.cloud_secret.clone());
         cloud.save_token = self.save_token.clone();
 
         cloud

From 6e46901dada60869a67624d02919ad62425b8e91 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Wed, 27 Mar 2024 14:08:29 +0200
Subject: [PATCH 239/284] reduce repetition and improving readability

---
 src/forms/cloud.rs        | 48 +++++++++++++++++----------------------
 tests/mock_data/README.md | 10 ++++++++
 2 files changed, 31 insertions(+), 27 deletions(-)
 create mode 100644 tests/mock_data/README.md

diff --git a/src/forms/cloud.rs b/src/forms/cloud.rs
index debf9758..2cdf6336 100644
--- a/src/forms/cloud.rs
+++ b/src/forms/cloud.rs
@@ -55,39 +55,33 @@ impl Cloud {
         }
     }
 
+    pub(crate) fn decrypt_field(
+        secret: &mut Secret,
+        field_name: &str,
+        encrypted_value: Option<String>,
+        reveal: bool,
+    ) -> Option<String> {
+        if let Some(val) = encrypted_value {
+            secret.field = field_name.to_owned();
+            let decoded_value = Cloud::decode(secret, val);
+            if reveal {
+                return Some(decoded_value);
+            } else {
+                return Some(hide_parts(decoded_value));
+            }
+        }
+        None
+    }
+
     // @todo should be refactored, may be moved to cloud.into() or Secret::from()
     pub(crate) fn decode_model(mut cloud: models::Cloud, reveal:bool) -> models::Cloud {
 
-        let encrypted_value = cloud.cloud_token.clone().unwrap();
         let mut secret = Secret::new();
         secret.user_id = cloud.user_id.clone();
         secret.project_id = cloud.project_id.clone().unwrap();
-
-        secret.field = "cloud_token".to_string();
-        let cloud_token = Cloud::decode(&mut secret, encrypted_value);
-        if reveal {
-            cloud.cloud_token = Some(cloud_token);
-        } else {
-            cloud.cloud_token = Some(hide_parts(cloud_token));
-        }
-
-        secret.field = "cloud_secret".to_string();
-        let encrypted_value = cloud.cloud_secret.clone().unwrap();
-        let cloud_secret = Cloud::decode(&mut secret, encrypted_value);
-        if reveal {
-            cloud.cloud_secret = Some(cloud_secret);
-        } else {
-            cloud.cloud_secret = Some(hide_parts(cloud_secret));
-        }
-
-        secret.field = "cloud_key".to_string();
-        let encrypted_value = cloud.cloud_key.clone().unwrap();
-        let cloud_key = Cloud::decode(&mut secret, encrypted_value);
-        if reveal {
-            cloud.cloud_key = Some(cloud_key);
-        } else {
-            cloud.cloud_key = Some(hide_parts(cloud_key));
-        }
+        cloud.cloud_token = Cloud::decrypt_field(&mut secret, "cloud_token", cloud.cloud_token.clone(), reveal);
+        cloud.cloud_secret = Cloud::decrypt_field(&mut secret, "cloud_secret", cloud.cloud_secret.clone(), reveal);
+        cloud.cloud_key = Cloud::decrypt_field(&mut secret, "cloud_key", cloud.cloud_key.clone(), reveal);
 
         cloud
     }
diff --git a/tests/mock_data/README.md b/tests/mock_data/README.md
new file mode 100644
index 00000000..4321e92a
--- /dev/null
+++ b/tests/mock_data/README.md
@@ -0,0 +1,10 @@
+This directory contains mock data for different endpoints
+
+Here is some examples of making requests using CURL
+
+Add new cloud credentials
+curl -X POST -v  http://localhost:8000/cloud -d @cloud.json  --header 'Content-Type: application/json' -H "Authorization: Bearer $TD_BEARER"
+
+Get cloud credentials
+curl -X GET  http://localhost:8000/cloud/31  --header 'Content-Type: application/json' -H "Authorization: Bearer $TD_BEARER" 
+

From 62ce2d8095a5d48dcb989ed4f51837090763b70c Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Wed, 27 Mar 2024 16:25:54 +0200
Subject: [PATCH 240/284] user project list

---
 src/routes/project/get.rs | 15 +++++++++++++++
 src/startup.rs            |  1 +
 2 files changed, 16 insertions(+)

diff --git a/src/routes/project/get.rs b/src/routes/project/get.rs
index aa03159c..cc9da9cf 100644
--- a/src/routes/project/get.rs
+++ b/src/routes/project/get.rs
@@ -27,6 +27,21 @@ pub async fn item(
         })
 }
 
+
+#[tracing::instrument(name = "Get project list.")]
+#[get("")]
+pub async fn list(
+    user: web::ReqData<Arc<models::User>>,
+    pg_pool: web::Data<PgPool>,
+) -> Result<impl Responder> {
+    db::project::fetch_by_user(pg_pool.get_ref(), &user.id)
+        .await
+        .map_err(|err| JsonResponse::internal_server_error(err))
+        .map(|projects| JsonResponse::build().set_list(projects).ok("OK"))
+}
+
+
+//admin's endpoint
 #[tracing::instrument(name = "Get user's project list.")]
 #[get("/user/{id}")]
 pub async fn admin_list(
diff --git a/src/startup.rs b/src/startup.rs
index 6fa0f6cb..a66b0e7f 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -70,6 +70,7 @@ pub async fn run(
                     .service(crate::routes::project::deploy::item)
                     .service(crate::routes::project::deploy::saved_item)
                     .service(crate::routes::project::compose::add)
+                    .service(crate::routes::project::get::list)
                     .service(crate::routes::project::get::item)
                     .service(crate::routes::project::add::item)
                     .service(crate::routes::project::update::item) 

From d1f40b75fea624b96bbf475b2d10f2f2a4a307b5 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Wed, 27 Mar 2024 19:37:14 +0200
Subject: [PATCH 241/284] redis creds to .env

---
 .env                          |  2 ++
 src/forms/project/deploy.rs   |  4 +++-
 src/helpers/cloud/security.rs |  6 +++++-
 src/routes/project/deploy.rs  | 14 +++++++-------
 4 files changed, 17 insertions(+), 9 deletions(-)

diff --git a/.env b/.env
index 1dcbed0b..0f19229d 100644
--- a/.env
+++ b/.env
@@ -6,3 +6,5 @@ POSTGRES_PASSWORD=postgres
 POSTGRES_DB=stacker
 POSTGRES_PORT=5432
 SECURITY_KEY=SECURITY_KEY_SHOULD_BE_OF_LEN_32
+
+REDIS_URL=redis://127.0.0.1/
\ No newline at end of file
diff --git a/src/forms/project/deploy.rs b/src/forms/project/deploy.rs
index ae4c5e8e..d0eee093 100644
--- a/src/forms/project/deploy.rs
+++ b/src/forms/project/deploy.rs
@@ -11,7 +11,9 @@ pub struct Deploy {
     #[validate]
     pub(crate) server: Server,
     #[validate]
-    pub(crate) cloud: Cloud
+    pub(crate) cloud: Cloud,
+    // pub user_id: Option<String>,
+    // pub project_id: Option<i32>
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
diff --git a/src/helpers/cloud/security.rs b/src/helpers/cloud/security.rs
index 29b997cf..af1e2569 100644
--- a/src/helpers/cloud/security.rs
+++ b/src/helpers/cloud/security.rs
@@ -27,7 +27,11 @@ impl Secret {
     }
     #[tracing::instrument(name = "Secret::connect_storage")]
     fn connect_storage() -> Connection {
-        match redis::Client::open("redis://127.0.0.1/"){
+
+        let storage_url = std::env::var("REDIS_URL")
+            .or_default("redis://127.0.0.1/".to_string());
+
+        match redis::Client::open(storage_url){
             Ok(client) => {
                 match client.get_connection() {
                     Ok(connection) => connection,
diff --git a/src/routes/project/deploy.rs b/src/routes/project/deploy.rs
index 7cdbb437..b1914651 100644
--- a/src/routes/project/deploy.rs
+++ b/src/routes/project/deploy.rs
@@ -17,7 +17,7 @@ use crate::helpers::compressor::compress;
 pub async fn item(
     user: web::ReqData<Arc<models::User>>,
     path: web::Path<(i32,)>,
-    form: web::Json<forms::project::Deploy>,
+    mut form: web::Json<forms::project::Deploy>,
     pg_pool: Data<PgPool>,
     mq_manager: Data<MqManager>,
     sets: Data<Settings>,
@@ -49,10 +49,10 @@ pub async fn item(
         JsonResponse::<models::Project>::build().internal_server_error(err)
     })?;
 
+    form.cloud.user_id = Some(user.id.clone());
+    form.cloud.project_id = Some(id);
     // Save cloud credentials if requested
     let mut cloud_creds: models::Cloud = (&form.cloud).into();
-    cloud_creds.project_id = Some(id);
-    cloud_creds.user_id = user.id.clone();
 
     if Some(true) == cloud_creds.save_token {
         db::cloud::insert(pg_pool.get_ref(), cloud_creds.clone())
@@ -105,7 +105,7 @@ pub async fn item(
         });
 
     tracing::debug!("Save deployment result: {:?}", result);
-    tracing::debug!("Send project data <<<<<<<<<<<>>>>>>>>>>>>>>>>{:?}", payload);
+    tracing::debug!("Send project data <<<>>>{:?}", payload);
 
     // Send Payload
     mq_manager
@@ -134,7 +134,7 @@ pub async fn saved_item(
 ) -> Result<impl Responder> {
     let id = path.0;
     let cloud_id = path.1;
-    //let cloud_id = Some(1);
+
     tracing::debug!("User {:?} is deploying project: {} to cloud: {} ", user, id, cloud_id);
 
     // Validate project
@@ -173,7 +173,7 @@ pub async fn saved_item(
             // if let Some(server) = server.into_iter().nth(0) {
             //     server
             // }
-            server.into_iter().nth(0).unwrap() // @todo refactoring is required
+            server.into_iter().nth(0).unwrap() //@todo refactoring is required for multiple types
         }
         Err(_e) => {
             return Err(JsonResponse::<models::Project>::build().not_found("No servers configured"));
@@ -210,7 +210,7 @@ pub async fn saved_item(
         });
 
     tracing::debug!("Save deployment result: {:?}", result);
-    tracing::debug!("Send project data <<<<<<<<<<<>>>>>>>>>>>>>>>>{:?}", payload);
+    tracing::debug!("Send project data <<<>>>{:?}", payload);
 
     // Send Payload
     mq_manager

From 330732decc3873f3d73c809b8803493abcecc051 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Wed, 27 Mar 2024 19:44:41 +0200
Subject: [PATCH 242/284] fix unwrap_or redis creds to .env

---
 src/helpers/cloud/security.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/helpers/cloud/security.rs b/src/helpers/cloud/security.rs
index af1e2569..3c9d6b53 100644
--- a/src/helpers/cloud/security.rs
+++ b/src/helpers/cloud/security.rs
@@ -29,7 +29,7 @@ impl Secret {
     fn connect_storage() -> Connection {
 
         let storage_url = std::env::var("REDIS_URL")
-            .or_default("redis://127.0.0.1/".to_string());
+            .unwrap_or("redis://127.0.0.1/".to_string());
 
         match redis::Client::open(storage_url){
             Ok(client) => {

From 8edd78303cbf8b20bc87b08dcb6274de6ee8aa33 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Wed, 27 Mar 2024 22:01:17 +0200
Subject: [PATCH 243/284] casbin-debug policies

---
 Cargo.lock                           | 1 +
 Cargo.toml                           | 1 +
 src/console/commands/debug/casbin.rs | 9 +++++++--
 3 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index f04999e9..c849d152 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3630,6 +3630,7 @@ dependencies = [
  "actix-http",
  "actix-web",
  "brotli",
+ "casbin",
  "chrono",
  "clap",
  "config",
diff --git a/Cargo.toml b/Cargo.toml
index 982000f3..d005e9be 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -58,6 +58,7 @@ deadpool-lapin = "0.11.0"
 docker-compose-types = "0.7.0"
 #actix-casbin-auth = { git = "https://github.com/casbin-rs/actix-casbin-auth.git"}
 actix-casbin-auth = { git = "https://github.com/smart--petea/actix-casbin-auth.git", branch = "dirty-master"}
+casbin = "2.2.0"
 
 [dependencies.sqlx]
 version = "0.6.3"
diff --git a/src/console/commands/debug/casbin.rs b/src/console/commands/debug/casbin.rs
index 6484ccab..19878218 100644
--- a/src/console/commands/debug/casbin.rs
+++ b/src/console/commands/debug/casbin.rs
@@ -1,7 +1,8 @@
 use crate::configuration::get_configuration;
 use actix_web::{rt, post, web, HttpResponse, Result, http::header::ContentType};
 use crate::middleware;
-use actix_casbin_auth::casbin::CoreApi;
+//use actix_casbin_auth::casbin::CoreApi;
+use casbin::CoreApi;
 use sqlx::PgPool;
 
 pub struct CasbinCommand {
@@ -27,10 +28,14 @@ impl crate::console::commands::CallableTrait for CasbinCommand {
             let settings = web::Data::new(settings);
             let db_pool = web::Data::new(db_pool);
 
+
             let mut authorizationService = middleware::authorization::try_new(settings.database.connection_string()).await?;
             let casbin_enforcer = authorizationService.get_enforcer();
             let mut lock = casbin_enforcer.write().await;
-            match lock.enforce_mut(vec![self.subject.clone(), self.path.clone(), self.action.clone()]) {
+            let policies = lock.get_model().get_model().get("p").unwrap().get("p").unwrap().get_policy();
+            println!("{}", policies.len());
+
+            match lock.enforce_mut(vec!["".to_string(), self.subject.clone(), self.path.clone(), self.action.clone()]) {
                 Ok(true) => println!("TRUE"),
                 Ok(false) => println!("FALSE"),
                 Err(err) => {

From 71edb3a48075123c39bbe6afede718b9f63d077f Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Thu, 28 Mar 2024 23:10:46 +0200
Subject: [PATCH 244/284] casbin-debug print all rules

---
 src/console/commands/debug/casbin.rs | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/console/commands/debug/casbin.rs b/src/console/commands/debug/casbin.rs
index 19878218..25137270 100644
--- a/src/console/commands/debug/casbin.rs
+++ b/src/console/commands/debug/casbin.rs
@@ -33,6 +33,10 @@ impl crate::console::commands::CallableTrait for CasbinCommand {
             let casbin_enforcer = authorizationService.get_enforcer();
             let mut lock = casbin_enforcer.write().await;
             let policies = lock.get_model().get_model().get("p").unwrap().get("p").unwrap().get_policy();
+            for policy in policies {
+                println!("{policy:?}");
+            }
+
             println!("{}", policies.len());
 
             match lock.enforce_mut(vec!["".to_string(), self.subject.clone(), self.path.clone(), self.action.clone()]) {

From 82a9e1f9582ab9c251de9562bdaede30db61c9e9 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Sat, 30 Mar 2024 20:00:46 +0200
Subject: [PATCH 245/284] server updates

---
 src/db/cloud.rs              |   9 ++-
 src/db/server.rs             |   6 +-
 src/forms/cloud.rs           | 134 +++++++++++++++++++++--------------
 src/forms/project/deploy.rs  |   6 +-
 src/forms/project/payload.rs |   2 +-
 src/forms/server.rs          |  12 ++++
 src/models/cloud.rs          |  43 ++++++++++-
 src/models/server.rs         |   2 +-
 src/models/user.rs           |   2 -
 src/routes/cloud/add.rs      |   6 +-
 src/routes/cloud/get.rs      |  16 ++++-
 src/routes/cloud/update.rs   |   2 +-
 src/routes/project/deploy.rs |  59 +++++++++++++--
 src/routes/project/update.rs |   3 +-
 14 files changed, 213 insertions(+), 89 deletions(-)

diff --git a/src/db/cloud.rs b/src/db/cloud.rs
index a5e71c2c..77c840e5 100644
--- a/src/db/cloud.rs
+++ b/src/db/cloud.rs
@@ -55,8 +55,9 @@ pub async fn insert(pool: &PgPool, mut cloud: models::Cloud) -> Result<models::C
         cloud_secret,
         save_token,
         created_at,
-        updated_at)
-        VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
+        updated_at
+        )
+        VALUES ($1, $2, $3, $4, $5, $6, $7, NOW() at time zone 'utc', NOW() at time zone 'utc')
         RETURNING id;
         "#,
         cloud.user_id,
@@ -65,9 +66,7 @@ pub async fn insert(pool: &PgPool, mut cloud: models::Cloud) -> Result<models::C
         cloud.cloud_token,
         cloud.cloud_key,
         cloud.cloud_secret,
-        cloud.save_token,
-        cloud.created_at,
-        cloud.updated_at,
+        cloud.save_token
     )
     .fetch_one(pool)
     .instrument(query_span)
diff --git a/src/db/server.rs b/src/db/server.rs
index 75513a99..60eafb1d 100644
--- a/src/db/server.rs
+++ b/src/db/server.rs
@@ -78,7 +78,7 @@ pub async fn insert(pool: &PgPool, mut server: models::Server) -> Result<models:
         disk_type,
         created_at,
         updated_at)
-        VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
+        VALUES ($1, $2, $3, $4, $5, $6, $7, NOW() at time zone 'utc',NOW() at time zone 'utc')
         RETURNING id;
         "#,
         server.user_id,
@@ -87,9 +87,7 @@ pub async fn insert(pool: &PgPool, mut server: models::Server) -> Result<models:
         server.zone,
         server.server,
         server.os,
-        server.disk_type,
-        server.created_at,
-        server.updated_at,
+        server.disk_type
     )
         .fetch_one(pool)
         .instrument(query_span)
diff --git a/src/forms/cloud.rs b/src/forms/cloud.rs
index 2cdf6336..704de558 100644
--- a/src/forms/cloud.rs
+++ b/src/forms/cloud.rs
@@ -3,48 +3,32 @@ use serde::{Deserialize, Serialize, Serializer};
 use serde_valid::Validate;
 use crate::helpers::cloud::security::Secret;
 use tracing::Instrument;
+use chrono::Utc;
 
 
-// fn hide_part<S>(value: &Option<String>, s: S) -> Result<S::Ok, S::Error>
-//     where
-//         S: Serializer,
-// {
-//     eprintln!("value in serde {:?}", value);
-//     let result: &str = match value {
-//         Some(value) => {
-//             let value = value.as_str();
-//             value
-//             // value.into_iter().take(6).collect::<String>()
-//         }
-//         None => "",
-//     };
-//     s.serialize_str(result)
-// }
 fn hide_parts(value: String) -> String {
     value.chars().into_iter().take(6).collect::<String>() + "****"
 }
 
-
 #[derive(Default, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct Cloud {
+pub struct CloudForm {
     pub user_id: Option<String>,
     pub project_id: Option<i32>,
     #[validate(min_length = 2)]
     #[validate(max_length = 50)]
     pub provider: String,
-    // #[serde(serialize_with = "hide_part")]
     pub cloud_token: Option<String>,
-    // #[serde(serialize_with = "hide_part")]
     pub cloud_key: Option<String>,
-    // #[serde(serialize_with = "hide_part")]
     pub cloud_secret: Option<String>,
     pub save_token: Option<bool>,
 }
 
-impl Cloud {
+impl CloudForm {
+    #[tracing::instrument(name = "impl CloudForm::decode()")]
     pub(crate) fn decode(secret: &mut Secret, encrypted_value: String) -> String {
         // tracing::error!("encrypted_value {:?}", &encrypted_value);
         let b64_decoded = Secret::b64_decode(&encrypted_value).unwrap();
+        // tracing::error!("decoded {:?}", &b64_decoded);
         match secret.decrypt(b64_decoded) {
             Ok(decoded) => decoded,
             Err(_err) => {
@@ -63,7 +47,7 @@ impl Cloud {
     ) -> Option<String> {
         if let Some(val) = encrypted_value {
             secret.field = field_name.to_owned();
-            let decoded_value = Cloud::decode(secret, val);
+            let decoded_value = CloudForm::decode(secret, val);
             if reveal {
                 return Some(decoded_value);
             } else {
@@ -74,20 +58,21 @@ impl Cloud {
     }
 
     // @todo should be refactored, may be moved to cloud.into() or Secret::from()
-    pub(crate) fn decode_model(mut cloud: models::Cloud, reveal:bool) -> models::Cloud {
+    #[tracing::instrument(name = "decode_model")]
+    pub fn decode_model(mut cloud: models::Cloud, reveal:bool) -> models::Cloud {
 
         let mut secret = Secret::new();
         secret.user_id = cloud.user_id.clone();
         secret.project_id = cloud.project_id.clone().unwrap();
-        cloud.cloud_token = Cloud::decrypt_field(&mut secret, "cloud_token", cloud.cloud_token.clone(), reveal);
-        cloud.cloud_secret = Cloud::decrypt_field(&mut secret, "cloud_secret", cloud.cloud_secret.clone(), reveal);
-        cloud.cloud_key = Cloud::decrypt_field(&mut secret, "cloud_key", cloud.cloud_key.clone(), reveal);
+        cloud.cloud_token = CloudForm::decrypt_field(&mut secret, "cloud_token", cloud.cloud_token.clone(), reveal);
+        cloud.cloud_secret = CloudForm::decrypt_field(&mut secret, "cloud_secret", cloud.cloud_secret.clone(), reveal);
+        cloud.cloud_key = CloudForm::decrypt_field(&mut secret, "cloud_key", cloud.cloud_key.clone(), reveal);
 
         cloud
     }
 }
 
-impl std::fmt::Debug for Cloud {
+impl std::fmt::Debug for CloudForm {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         let cloud_key: String = match self.cloud_key.as_ref() {
             Some(val) =>
@@ -135,22 +120,30 @@ fn encrypt_field(
     None
 }
 
-impl Into<models::Cloud> for &Cloud {
+impl Into<models::Cloud> for &CloudForm {
+    #[tracing::instrument(name = "impl Into<models::Cloud> for &CloudForm")]
     fn into(self) -> models::Cloud {
         let mut cloud = models::Cloud::default();
         cloud.provider = self.provider.clone();
         cloud.user_id = self.user_id.clone().unwrap();
         cloud.project_id = self.project_id;
 
-        let mut secret = Secret::new();
-        secret.user_id = self.user_id.clone().unwrap();
-        secret.project_id = self.project_id.unwrap();
-
-        cloud.cloud_token = encrypt_field(&mut secret, "cloud_token", self.cloud_token.clone());
-        cloud.cloud_key = encrypt_field(&mut secret, "cloud_key", self.cloud_key.clone());
-        cloud.cloud_secret = encrypt_field(&mut secret, "cloud_secret", self.cloud_secret.clone());
+        if Some(true) == self.save_token {
+            let mut secret = Secret::new();
+            secret.user_id = self.user_id.clone().unwrap();
+            secret.project_id = self.project_id.unwrap();
+
+            cloud.cloud_token = encrypt_field(&mut secret, "cloud_token", self.cloud_token.clone());
+            cloud.cloud_key = encrypt_field(&mut secret, "cloud_key", self.cloud_key.clone());
+            cloud.cloud_secret = encrypt_field(&mut secret, "cloud_secret", self.cloud_secret.clone());
+        } else {
+            cloud.cloud_token = self.cloud_token.clone();
+            cloud.cloud_key = self.cloud_key.clone();
+            cloud.cloud_secret = self.cloud_secret.clone();
+        }
         cloud.save_token = self.save_token.clone();
-
+        cloud.created_at = Utc::now();
+        cloud.updated_at = Utc::now();
         cloud
     }
 
@@ -158,30 +151,61 @@ impl Into<models::Cloud> for &Cloud {
 
 
 // on deploy
-impl Into<Cloud> for models::Cloud {
-    fn into(self) -> Cloud {
-        let mut form = Cloud::default();
+impl Into<CloudForm> for models::Cloud {
+    #[tracing::instrument(name = "Into<CloudForm> for models::Cloud .")]
+    fn into(self) -> CloudForm {
+        let mut form = CloudForm::default();
         form.project_id = self.project_id;
         form.provider = self.provider;
 
-        let mut secret = Secret::new();
-        secret.user_id = self.user_id.clone();
-        secret.project_id = self.project_id.clone().unwrap();
-
-        secret.field = "cloud_token".to_string();
-        let cloud_token = Cloud::decode(&mut secret, self.cloud_token.unwrap());
-        form.cloud_token = Some(cloud_token);
-
-        secret.field = "cloud_key".to_string();
-        let cloud_key = Cloud::decode(&mut secret, self.cloud_key.unwrap());
-        form.cloud_token = Some(cloud_key);
-
-        secret.field = "cloud_secret".to_string();
-        let cloud_secret = Cloud::decode(&mut secret, self.cloud_secret.unwrap());
-        form.cloud_secret = Some(cloud_secret);
+        if Some(true) == self.save_token {
+            let mut secret = Secret::new();
+            secret.user_id = self.user_id.clone();
+            secret.project_id = self.project_id.unwrap();
+            secret.field = "cloud_token".to_string();
+
+            let value = match self.cloud_token {
+                Some(value) => {
+                    CloudForm::decode(&mut secret, value)
+                }
+                None => {
+                    tracing::debug!("Skip {}", secret.field);
+                    "".to_string()
+                }
+            };
+            form.cloud_token = Some(value);
+
+            secret.field = "cloud_key".to_string();
+            let value = match self.cloud_key {
+                Some(value) => {
+                    CloudForm::decode(&mut secret, value)
+                }
+                None => {
+                    tracing::debug!("Skipp {}", secret.field);
+                    "".to_string()
+                }
+            };
+            form.cloud_key = Some(value);
+
+            secret.field = "cloud_secret".to_string();
+            let value = match self.cloud_secret {
+                Some(value) => {
+                    CloudForm::decode(&mut secret, value)
+                }
+                None => {
+                    tracing::debug!("Skipp {}", secret.field);
+                    "".to_string()
+                }
+            };
+            form.cloud_secret = Some(value);
+
+        } else {
+            form.cloud_token = self.cloud_token;
+            form.cloud_key = self.cloud_key;
+            form.cloud_secret = self.cloud_secret;
+        }
 
         form.save_token = self.save_token;
-
         form
     }
 }
diff --git a/src/forms/project/deploy.rs b/src/forms/project/deploy.rs
index d0eee093..86c1453f 100644
--- a/src/forms/project/deploy.rs
+++ b/src/forms/project/deploy.rs
@@ -2,7 +2,7 @@ use serde_derive::{Deserialize, Serialize};
 use serde_json::Value;
 use serde_valid::Validate;
 use crate::forms;
-use crate::forms::{Cloud, Server};
+use crate::forms::{CloudForm, Server};
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Deploy {
@@ -11,9 +11,7 @@ pub struct Deploy {
     #[validate]
     pub(crate) server: Server,
     #[validate]
-    pub(crate) cloud: Cloud,
-    // pub user_id: Option<String>,
-    // pub project_id: Option<i32>
+    pub(crate) cloud: CloudForm,
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
diff --git a/src/forms/project/payload.rs b/src/forms/project/payload.rs
index d7e84d4b..5eb115a5 100644
--- a/src/forms/project/payload.rs
+++ b/src/forms/project/payload.rs
@@ -12,7 +12,7 @@ pub struct Payload {
     pub(crate) user_token: Option<String>,
     pub(crate) user_email: Option<String>,
     #[serde(flatten)]
-    pub cloud: Option<forms::Cloud>,
+    pub cloud: Option<forms::CloudForm>,
     #[serde(flatten)]
     pub server: Option<forms::Server>,
     #[serde(flatten)]
diff --git a/src/forms/server.rs b/src/forms/server.rs
index cba997f9..8b78c14e 100644
--- a/src/forms/server.rs
+++ b/src/forms/server.rs
@@ -29,6 +29,18 @@ impl Into<models::Server> for &Server {
     }
 }
 
+// impl From<Server> for models::Server {
+//     fn from(self, value: Server) -> &mut Self {
+//         self.disk_type = value.disk_type.clone();
+//         self.region = value.region.clone();
+//         self.server = value.server.clone();
+//         self.zone = value.zone.clone();
+//         self.os = value.os.clone();
+//         self.updated_at = Utc::now();
+//         self
+//     }
+// }
+
 impl Into<Server> for models::Server {
 
     fn into(self) -> Server {
diff --git a/src/models/cloud.rs b/src/models/cloud.rs
index 3c2a7521..77c3224a 100644
--- a/src/models/cloud.rs
+++ b/src/models/cloud.rs
@@ -1,7 +1,7 @@
 use chrono::{DateTime, Utc};
 use serde_derive::{Deserialize, Serialize};
 
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct Cloud {
     pub id: i32,
     pub user_id: String,
@@ -37,3 +37,44 @@ impl std::fmt::Display for Cloud {
         )
     }
 }
+
+impl Cloud {
+    pub fn new(user_id: String,
+               project_id: Option<i32>,
+               provider: String,
+               cloud_token: Option<String>,
+               cloud_key: Option<String>,
+               cloud_secret: Option<String>,
+               save_token: Option<bool>
+    ) -> Self {
+        Self {
+            id: 0,
+            user_id,
+            project_id,
+            provider,
+            cloud_token,
+            cloud_key,
+            cloud_secret,
+            save_token,
+            created_at: Utc::now(),
+            updated_at: Utc::now(),
+        }
+    }
+}
+
+impl Default for Cloud {
+    fn default() -> Self {
+        Cloud {
+            id: 0,
+            provider: "".to_string(),
+            user_id: "".to_string(),
+            project_id: Default::default(),
+            cloud_key: Default::default(),
+            cloud_token: Default::default(),
+            cloud_secret: Default::default(),
+            save_token: Some(false),
+            created_at: Default::default(),
+            updated_at: Default::default(),
+        }
+    }
+}
diff --git a/src/models/server.rs b/src/models/server.rs
index 3827fcf3..3e575a1a 100644
--- a/src/models/server.rs
+++ b/src/models/server.rs
@@ -24,4 +24,4 @@ pub struct Server {
     pub disk_type: Option<String>,
     pub created_at: DateTime<Utc>,
     pub updated_at: DateTime<Utc>,
-}
+}
\ No newline at end of file
diff --git a/src/models/user.rs b/src/models/user.rs
index 8217ac80..21111431 100644
--- a/src/models/user.rs
+++ b/src/models/user.rs
@@ -7,6 +7,4 @@ pub struct User {
     pub last_name: String,
     pub email: String,
     pub email_confirmed: bool,
-    // pub phone: Option<String>,
-    // pub website: Option<String>,
 }
diff --git a/src/routes/cloud/add.rs b/src/routes/cloud/add.rs
index ce4126fa..ebd261e5 100644
--- a/src/routes/cloud/add.rs
+++ b/src/routes/cloud/add.rs
@@ -15,7 +15,7 @@ use tracing::Instrument;
 #[post("")]
 pub async fn add(
     user: web::ReqData<Arc<models::User>>,
-    mut form: web::Json<forms::cloud::Cloud>,
+    mut form: web::Json<forms::cloud::CloudForm>,
     pg_pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
 
@@ -28,9 +28,7 @@ pub async fn add(
     }
 
     form.user_id = Some(user.id.clone());
-    let mut cloud: models::Cloud = form.deref().into();
-    cloud.created_at = Utc::now();
-    cloud.updated_at = Utc::now();
+    let cloud: models::Cloud = form.deref().into();
 
     db::cloud::insert(pg_pool.get_ref(), cloud)
         .await
diff --git a/src/routes/cloud/get.rs b/src/routes/cloud/get.rs
index 5f0e7f9b..43ac8012 100644
--- a/src/routes/cloud/get.rs
+++ b/src/routes/cloud/get.rs
@@ -4,7 +4,7 @@ use crate::helpers::JsonResponse;
 use crate::models;
 use actix_web::{get, web, Responder, Result};
 use sqlx::PgPool;
-use crate::forms::Cloud;
+use crate::forms::CloudForm;
 use tracing::Instrument;
 
 #[tracing::instrument(name = "Get cloud credentials.")]
@@ -25,7 +25,7 @@ pub async fn item(
                     Err(JsonResponse::not_found("record not found"))
                 },
                 Some(cloud) => {
-                    let cloud = Cloud::decode_model(cloud, false);
+                    let cloud = CloudForm::decode_model(cloud, false);
                     Ok(JsonResponse::build().set_item(Some(cloud)).ok("OK"))
                 },
                 None => Err(JsonResponse::not_found("record not found")),
@@ -43,6 +43,16 @@ pub async fn list(
 ) -> Result<impl Responder> {
     db::cloud::fetch_by_user(pg_pool.get_ref(), user.id.as_ref())
         .await
-        .map(|clouds| JsonResponse::build().set_list(clouds).ok("OK"))
+        .map(|clouds| {
+
+            let clouds = clouds
+                .into_iter()
+                .map(|cloud| CloudForm::decode_model(cloud, false) )
+                // .map_err(|e| tracing::error!("Failed to decode cloud, {:?}", e))
+                .collect();
+
+            JsonResponse::build().set_list(clouds).ok("OK")
+
+        })
         .map_err(|_err| JsonResponse::<models::Cloud>::build().internal_server_error(""))
 }
diff --git a/src/routes/cloud/update.rs b/src/routes/cloud/update.rs
index 3c603603..7672f551 100644
--- a/src/routes/cloud/update.rs
+++ b/src/routes/cloud/update.rs
@@ -14,7 +14,7 @@ use chrono::Utc;
 #[put("/{id}")]
 pub async fn item(
     path: web::Path<(i32,)>,
-    form: web::Json<forms::cloud::Cloud>,
+    form: web::Json<forms::cloud::CloudForm>,
     user: web::ReqData<Arc<models::User>>,
     pg_pool: Data<PgPool>,
 ) -> Result<impl Responder> {
diff --git a/src/routes/project/deploy.rs b/src/routes/project/deploy.rs
index b1914651..3a9bea3d 100644
--- a/src/routes/project/deploy.rs
+++ b/src/routes/project/deploy.rs
@@ -9,6 +9,7 @@ use sqlx::PgPool;
 use std::sync::Arc;
 use serde_valid::Validate;
 use crate::helpers::compressor::compress;
+use chrono::{Utc};
 
 
 
@@ -52,7 +53,9 @@ pub async fn item(
     form.cloud.user_id = Some(user.id.clone());
     form.cloud.project_id = Some(id);
     // Save cloud credentials if requested
-    let mut cloud_creds: models::Cloud = (&form.cloud).into();
+    let cloud_creds: models::Cloud = (&form.cloud).into();
+
+    // let cloud_creds = forms::Cloud::decode_model(cloud_creds, false);
 
     if Some(true) == cloud_creds.save_token {
         db::cloud::insert(pg_pool.get_ref(), cloud_creds.clone())
@@ -127,6 +130,7 @@ pub async fn item(
 #[post("/{id}/deploy/{cloud_id}")]
 pub async fn saved_item(
     user: web::ReqData<Arc<models::User>>,
+    mut form: web::Json<forms::project::Deploy>,
     path: web::Path<(i32, i32)>,
     pg_pool: Data<PgPool>,
     mq_manager: Data<MqManager>,
@@ -137,6 +141,14 @@ pub async fn saved_item(
 
     tracing::debug!("User {:?} is deploying project: {} to cloud: {} ", user, id, cloud_id);
 
+    if !form.validate().is_ok() {
+        let errors = form.validate().unwrap_err().to_string();
+        let err_msg = format!("Invalid form data received {:?}", &errors);
+        tracing::debug!(err_msg);
+
+        return Err(JsonResponse::<models::Project>::build().form_error(errors));
+    }
+
     // Validate project
     let project = db::project::fetch(pg_pool.get_ref(), id)
         .await
@@ -169,26 +181,56 @@ pub async fn saved_item(
 
     let server = match db::server::fetch_by_project(pg_pool.get_ref(), dc.project.id.clone()).await {
         Ok(server) => {
-            // for now we support only one type of servers
-            // if let Some(server) = server.into_iter().nth(0) {
-            //     server
-            // }
-            server.into_iter().nth(0).unwrap() //@todo refactoring is required for multiple types
+            // currently we support only one type of servers
+            //@todo multiple server types support
+            match server.into_iter().nth(0) {
+                Some(mut server) =>  {
+                    // new updates
+                    server.disk_type = form.server.disk_type.clone();
+                    server.region = form.server.region.clone();
+                    server.server = form.server.server.clone();
+                    server.zone = form.server.zone.clone();
+                    server.os = form.server.os.clone();
+                    server.user_id = user.id.clone();
+                    server.project_id = id;
+                    server
+                },
+                None => {
+                    // Create new server
+                    let mut server: models::Server = (&form.server).into();
+                    server.user_id = user.id.clone();
+                    server.project_id = id;
+                    db::server::insert(pg_pool.get_ref(), server)
+                        .await
+                        .map(|server| server)
+                        .map_err(|_| {
+                            JsonResponse::<models::Server>::build().internal_server_error("Internal Server Error")
+                        })?
+                }
+            }
         }
         Err(_e) => {
             return Err(JsonResponse::<models::Project>::build().not_found("No servers configured"));
         }
     };
 
+    let server = db::server::update(pg_pool.get_ref(), server)
+        .await
+        .map(|server| server)
+        .map_err(|_| {
+            JsonResponse::<models::Server>::build().internal_server_error("Internal Server Error")
+        })?;
+
+    // Building Payload for the 3-d party service through RabbitMQ
     // let mut payload = forms::project::Payload::default();
     let mut payload = forms::project::Payload::try_from(&dc.project)
         .map_err(|err| JsonResponse::<models::Project>::build().bad_request(err))?;
 
     payload.server = Some(server.into());
     payload.cloud  = Some(cloud.into());
+    payload.stack  = form.stack.clone().into();
     payload.user_token = Some(user.id.clone());
     payload.user_email = Some(user.email.clone());
-    // let compressed = fc.unwrap_or("".to_string());
     payload.docker_compose = Some(compress(fc.as_str()));
 
     // Store deployment attempts into deployment table in db
@@ -228,3 +270,6 @@ pub async fn saved_item(
         })
 
 }
+
+
+
diff --git a/src/routes/project/update.rs b/src/routes/project/update.rs
index 3e1bb19e..cdd532e6 100644
--- a/src/routes/project/update.rs
+++ b/src/routes/project/update.rs
@@ -42,7 +42,8 @@ pub async fn item(
     let project_name = form.custom.custom_stack_code.clone();
 
     if Ok(false) == form.is_readable_docker_image().await {
-        return Err(JsonResponse::<models::Project>::build().bad_request("Can not access docker image"));
+        return Err(JsonResponse::<models::Project>::build()
+            .bad_request("Can not access docker image"));
     }
 
     let body: Value = serde_json::to_value::<ProjectForm>(form)

From 75d2c4dbc8386e4d65193dd51340915f09979ad0 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Sat, 30 Mar 2024 20:12:55 +0200
Subject: [PATCH 246/284] Check casbin rule command, readme

---
 README.md                            | 6 +++++-
 src/console/commands/debug/casbin.rs | 2 +-
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 09f466ff..ad44ea73 100644
--- a/README.md
+++ b/README.md
@@ -84,4 +84,8 @@ curl -X POST -H "Content-Type: application/json" -d @custom-stack-payload-2.json
 curl -X POST http://localhost:8000/client  --header 'Content-Type: application/json' -H "Authorization: Bearer $TD_BEARER"
 
 test client deploy
-http://localhost:8000/test/deploy
\ No newline at end of file
+http://localhost:8000/test/deploy
+
+
+Test casbin rule
+cargo r --bin console debug casbin --path "/project/:id" --action "POST" --subject "hy181TZa4DaabUZWklsrxw"
\ No newline at end of file
diff --git a/src/console/commands/debug/casbin.rs b/src/console/commands/debug/casbin.rs
index 25137270..b05fb2e2 100644
--- a/src/console/commands/debug/casbin.rs
+++ b/src/console/commands/debug/casbin.rs
@@ -39,7 +39,7 @@ impl crate::console::commands::CallableTrait for CasbinCommand {
 
             println!("{}", policies.len());
 
-            match lock.enforce_mut(vec!["".to_string(), self.subject.clone(), self.path.clone(), self.action.clone()]) {
+            match lock.enforce_mut(vec![self.subject.clone(), self.path.clone(), self.action.clone()]) {
                 Ok(true) => println!("TRUE"),
                 Ok(false) => println!("FALSE"),
                 Err(err) => {

From 865882d744c13d129bd87d5df3bca4b15c0e2d3a Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Sat, 30 Mar 2024 20:41:27 +0200
Subject: [PATCH 247/284] enable casbin, use security key from env

---
 src/helpers/cloud/security.rs | 19 +++++++++----------
 src/startup.rs                |  2 +-
 2 files changed, 10 insertions(+), 11 deletions(-)

diff --git a/src/helpers/cloud/security.rs b/src/helpers/cloud/security.rs
index 3c9d6b53..2e389940 100644
--- a/src/helpers/cloud/security.rs
+++ b/src/helpers/cloud/security.rs
@@ -84,12 +84,12 @@ impl Secret {
     #[tracing::instrument(name = "encrypt.")]
     pub fn encrypt(&self, token: String) -> Result<Vec<u8>, String> {
 
-        // let sec_key = std::env::var("SECURITY_KEY")
-        //     .expect("SECURITY_KEY environment variable is not set")
-        //     .as_bytes();
-        let sec_key = "SECURITY_KEY_SHOULD_BE_OF_LEN_32".as_bytes();
+        let sec_key = std::env::var("SECURITY_KEY")
+            .expect("SECURITY_KEY environment variable is not set")
+            .clone();
+
         // let key = Aes256Gcm::generate_key(OsRng);
-        let key: &Key::<Aes256Gcm> = Key::<Aes256Gcm>::from_slice(&sec_key);
+        let key: &Key::<Aes256Gcm> = Key::<Aes256Gcm>::from_slice(&sec_key.as_bytes());
         // eprintln!("encrypt key {key:?}");
         // eprintln!("encrypt: from slice key {key:?}");
         let cipher = Aes256Gcm::new(&key);
@@ -113,11 +113,10 @@ impl Secret {
 
     #[tracing::instrument(name = "decrypt.")]
     pub fn decrypt(&mut self, encrypted_data: Vec<u8>) -> Result<String, String> {
-        let sec_key = "SECURITY_KEY_SHOULD_BE_OF_LEN_32".as_bytes();
-        // let sec_key = std::env::var("SECURITY_KEY")
-        //     .expect("SECURITY_KEY environment variable is not set")
-        //     .as_bytes();
-        let key: &Key::<Aes256Gcm> = Key::<Aes256Gcm>::from_slice(&sec_key);
+        let sec_key = std::env::var("SECURITY_KEY")
+            .expect("SECURITY_KEY environment variable is not set")
+            .clone();
+        let key: &Key::<Aes256Gcm> = Key::<Aes256Gcm>::from_slice(&sec_key.as_bytes());
         // eprintln!("decrypt: Key str {key:?}");
         let rkey = format!("{}_{}_{}", self.user_id, self.project_id, self.field);
         eprintln!("decrypt: Key str {rkey:?}");
diff --git a/src/startup.rs b/src/startup.rs
index a66b0e7f..302070d0 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -42,7 +42,7 @@ pub async fn run(
     let server = HttpServer::new(move || {
         App::new()
             .wrap(TracingLogger::default())
-            // .wrap(authorization.clone())
+            .wrap(authorization.clone())
             .wrap(middleware::authentication::Manager::new())
             .wrap(Cors::permissive())
             .service(

From 45839648c4356f4433c2708091bc97ab35e9cf79 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Mon, 1 Apr 2024 13:34:53 +0300
Subject: [PATCH 248/284] initial casbin rules

---
 docker-compose.yml                            |  2 +-
 docker/dev/docker-compose.yml                 |  2 +-
 ...240401103123_casbin_initial_rules.down.sql |  1 +
 ...20240401103123_casbin_initial_rules.up.sql | 30 +++++++++++++++++++
 src/console/commands/appclient/new.rs         |  1 +
 src/forms/user.rs                             |  2 ++
 .../authentication/method/f_oauth.rs          |  7 +++--
 src/models/user.rs                            |  1 +
 src/routes/project/deploy.rs                  |  1 +
 9 files changed, 42 insertions(+), 5 deletions(-)
 create mode 100644 migrations/20240401103123_casbin_initial_rules.down.sql
 create mode 100644 migrations/20240401103123_casbin_initial_rules.up.sql

diff --git a/docker-compose.yml b/docker-compose.yml
index 4ba581c1..808216e8 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -11,7 +11,7 @@ volumes:
 services:
 
   stacker:
-    image: trydirect/stacker:0.0.7
+    image: trydirect/stacker:0.0.8
     build: .
     container_name: stacker
     restart: always
diff --git a/docker/dev/docker-compose.yml b/docker/dev/docker-compose.yml
index eeac0386..af707ba0 100644
--- a/docker/dev/docker-compose.yml
+++ b/docker/dev/docker-compose.yml
@@ -16,7 +16,7 @@ networks:
 services:
 
   stacker:
-    image: trydirect/stacker:0.0.7
+    image: trydirect/stacker:0.0.8
     build: .
     container_name: stacker
     restart: always
diff --git a/migrations/20240401103123_casbin_initial_rules.down.sql b/migrations/20240401103123_casbin_initial_rules.down.sql
new file mode 100644
index 00000000..d2f607c5
--- /dev/null
+++ b/migrations/20240401103123_casbin_initial_rules.down.sql
@@ -0,0 +1 @@
+-- Add down migration script here
diff --git a/migrations/20240401103123_casbin_initial_rules.up.sql b/migrations/20240401103123_casbin_initial_rules.up.sql
new file mode 100644
index 00000000..cbd06691
--- /dev/null
+++ b/migrations/20240401103123_casbin_initial_rules.up.sql
@@ -0,0 +1,30 @@
+-- Add up migration script here
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (1, 'p', 'group_admin', '/client', 'POST', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (2, 'g', 'admin_petru', 'group_admin', '', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (3, 'g', 'user_petru', 'group_user', '', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (4, 'p', 'group_user', '/client/:id/disable', 'PUT', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (5, 'p', 'group_admin', '/admin/client/:id/disable', 'PUT', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (6, 'p', 'group_admin', '/admin/client/:id/enable', 'PUT', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (7, 'p', 'group_user', '/client/:id/enable', 'PUT', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (8, 'p', 'group_user', '/client/:id', 'PUT', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (9, 'p', 'group_admin', '/admin/client/:id', 'PUT', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (10, 'g', 'anonym', 'group_anonymous', '', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (11, 'p', 'group_anonymous', '/health_check', 'GET', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (12, 'p', 'group_anonymous', '/rating/:id', 'GET', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (13, 'p', 'group_user', '/rating/:id', 'GET', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (14, 'p', 'group_admin', '/rating/:id', 'GET', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (15, 'p', 'group_user', '/rating', 'GET', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (16, 'p', 'group_admin', '/rating', 'GET', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (17, 'p', 'group_anonymous', '/rating', 'GET', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (18, 'p', 'group_user', '/rating', 'POST', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (19, 'p', 'group_admin', '/admin/project/user/:userid', 'GET', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (21, 'p', 'group_user', '/project', 'POST', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (33, 'g', 'group_admin', 'group_anonymous', '', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (34, 'g', 'group_user', 'group_anonymous', '', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (37, 'p', 'group_user', '/project/:id', 'GET', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (36, 'p', 'group_user', '/project/:id/compose', 'GET', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (38, 'p', 'group_user', '/project/:id/deploy', 'POST', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (39, 'p', 'group_user', '/project', 'GET', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (40, 'g', 'user', 'group_user', '', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (41, 'p', 'group_user', '/project/:id/deploy/:cloud_id', 'POST', '', '', '');
+
diff --git a/src/console/commands/appclient/new.rs b/src/console/commands/appclient/new.rs
index ada01acb..accbd765 100644
--- a/src/console/commands/appclient/new.rs
+++ b/src/console/commands/appclient/new.rs
@@ -31,6 +31,7 @@ impl crate::console::commands::CallableTrait for NewCommand {
                 last_name: "last_name".to_string(),
                 email: "email".to_string(),
                 email_confirmed: true,
+                role: "role".to_string()
             };
             crate::routes::client::add_handler_inner(&user.id, settings, db_pool).await?;
 
diff --git a/src/forms/user.rs b/src/forms/user.rs
index 5a7fa48f..0c58d75f 100644
--- a/src/forms/user.rs
+++ b/src/forms/user.rs
@@ -56,6 +56,7 @@ pub struct User {
     pub deployments_left: Value,
     #[serde(rename = "suspension_hints")]
     pub suspension_hints: Option<SuspensionHints>,
+    pub role: String
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
@@ -137,6 +138,7 @@ impl TryInto<UserModel> for UserForm {
             last_name: self.user.last_name.unwrap_or("Noname".to_string()),
             email: self.user.email,
             email_confirmed: self.user.email_confirmed,
+            role: self.user.role
         })
     }
 
diff --git a/src/middleware/authentication/method/f_oauth.rs b/src/middleware/authentication/method/f_oauth.rs
index 8400e81d..cec47b4c 100644
--- a/src/middleware/authentication/method/f_oauth.rs
+++ b/src/middleware/authentication/method/f_oauth.rs
@@ -14,7 +14,8 @@ fn try_extract_token(authentication: String) -> Result<String, String> {
     }
     let token = authentication_parts.next();
     if token.is_none() {
-        return Err("Empty bearer token".to_string());
+        tracing::error!("Bearer token is missing");
+        return Err("Authentication required".to_string());
     }
 
     Ok(token.unwrap().into())
@@ -33,9 +34,9 @@ pub async fn try_oauth(req: &mut ServiceRequest) -> Result<bool, String> {
         .await
         .map_err(|err| format!("{err}"))?;
 
-    // println!("user ================== {}", user.id.clone());
+    // control access using user role
     let acl_vals = actix_casbin_auth::CasbinVals {
-        subject: user.id.clone(),
+        subject: user.role.clone(),
         domain: None,
     };
 
diff --git a/src/models/user.rs b/src/models/user.rs
index 21111431..0f6b1efd 100644
--- a/src/models/user.rs
+++ b/src/models/user.rs
@@ -6,5 +6,6 @@ pub struct User {
     pub first_name: String,
     pub last_name: String,
     pub email: String,
+    pub role: String,
     pub email_confirmed: bool,
 }
diff --git a/src/routes/project/deploy.rs b/src/routes/project/deploy.rs
index 3a9bea3d..661115e0 100644
--- a/src/routes/project/deploy.rs
+++ b/src/routes/project/deploy.rs
@@ -197,6 +197,7 @@ pub async fn saved_item(
                 },
                 None => {
                     // Create new server
+                    // form.update_with(server.into());
                     let mut server: models::Server = (&form.server).into();
                     server.user_id = user.id.clone();
                     server.project_id = id;

From 3e50dcbe3a3c10f8072432cd766697e9d6980143 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Mon, 1 Apr 2024 13:38:22 +0300
Subject: [PATCH 249/284] initial casbin rules

---
 docker/dev/docker-compose.yml | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/docker/dev/docker-compose.yml b/docker/dev/docker-compose.yml
index af707ba0..6f8c0aba 100644
--- a/docker/dev/docker-compose.yml
+++ b/docker/dev/docker-compose.yml
@@ -4,7 +4,7 @@ volumes:
   stackerdb:
     driver: local
 
-  redis-data:
+  stacker-redis-data:
     driver: local
 
 networks:
@@ -13,6 +13,7 @@ networks:
     name: backend
     external: true
 
+
 services:
 
   stacker:
@@ -32,7 +33,7 @@ services:
       - ./.env
     environment:
       - RUST_LOG=debug
-      - RUST_BACKTRACE=1
+      - RUST_BACKTRACE=full
     depends_on:
       stackerdb:
         condition: service_healthy
@@ -79,14 +80,14 @@ services:
     networks:
       - backend
 
-  redis:
+  stackerredis:
     container_name: stackerredis
     image: redis:latest
     restart: always
     ports:
-      - 6379
+      - 127.0.0.1:6379:6379
     volumes:
-      - redis-data:/data
+      - stacker-redis-data:/data
     #        - ./redis/rc.local:/etc/rc.local
     #        - ./redis/redis.conf:/usr/local/etc/redis/redis.conf
     sysctls:

From 820f52232703b3e7464c0e1fd618db437f660b92 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Mon, 1 Apr 2024 22:13:45 +0300
Subject: [PATCH 250/284] remove project_id from cloud, as cloud keys can be
 use across projects

---
 Dockerfile                                    |  5 --
 ...20240401103123_casbin_initial_rules.up.sql | 70 +++++++++++--------
 ...4313_remove_project_id_from_cloud.down.sql |  2 +
 ...184313_remove_project_id_from_cloud.up.sql |  3 +
 src/console/commands/appclient/new.rs         |  2 +-
 src/db/cloud.rs                               | 16 ++---
 src/forms/cloud.rs                            | 11 ++-
 src/forms/project/deploy.rs                   |  4 +-
 src/forms/project/payload.rs                  |  2 +-
 src/forms/server.rs                           | 22 ++----
 src/helpers/cloud/security.rs                 |  8 +--
 .../authentication/method/f_oauth.rs          |  1 +
 src/models/cloud.rs                           |  7 +-
 src/routes/project/deploy.rs                  |  4 +-
 src/routes/server/update.rs                   |  2 +-
 15 files changed, 76 insertions(+), 83 deletions(-)
 create mode 100644 migrations/20240401184313_remove_project_id_from_cloud.down.sql
 create mode 100644 migrations/20240401184313_remove_project_id_from_cloud.up.sql

diff --git a/Dockerfile b/Dockerfile
index 66222d1e..8d969e75 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -31,11 +31,6 @@ COPY ./src ./src
 #RUN sqlx migrate run
 #RUN cargo sqlx prepare -- --bin stacker
 
-# rebuild app with project source
-#RUN rm -rf ./target/release/deps/server*; \
-#    apt-get install --no-install-recommends -y libssl-dev; \
-#    cargo build --release
-
 RUN apt-get update && apt-get install --no-install-recommends -y libssl-dev; \
     cargo build --bin=console && cargo build --release
 
diff --git a/migrations/20240401103123_casbin_initial_rules.up.sql b/migrations/20240401103123_casbin_initial_rules.up.sql
index cbd06691..effa7035 100644
--- a/migrations/20240401103123_casbin_initial_rules.up.sql
+++ b/migrations/20240401103123_casbin_initial_rules.up.sql
@@ -1,30 +1,42 @@
--- Add up migration script here
-INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (1, 'p', 'group_admin', '/client', 'POST', '', '', '');
-INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (2, 'g', 'admin_petru', 'group_admin', '', '', '', '');
-INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (3, 'g', 'user_petru', 'group_user', '', '', '', '');
-INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (4, 'p', 'group_user', '/client/:id/disable', 'PUT', '', '', '');
-INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (5, 'p', 'group_admin', '/admin/client/:id/disable', 'PUT', '', '', '');
-INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (6, 'p', 'group_admin', '/admin/client/:id/enable', 'PUT', '', '', '');
-INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (7, 'p', 'group_user', '/client/:id/enable', 'PUT', '', '', '');
-INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (8, 'p', 'group_user', '/client/:id', 'PUT', '', '', '');
-INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (9, 'p', 'group_admin', '/admin/client/:id', 'PUT', '', '', '');
-INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (10, 'g', 'anonym', 'group_anonymous', '', '', '', '');
-INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (11, 'p', 'group_anonymous', '/health_check', 'GET', '', '', '');
-INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (12, 'p', 'group_anonymous', '/rating/:id', 'GET', '', '', '');
-INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (13, 'p', 'group_user', '/rating/:id', 'GET', '', '', '');
-INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (14, 'p', 'group_admin', '/rating/:id', 'GET', '', '', '');
-INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (15, 'p', 'group_user', '/rating', 'GET', '', '', '');
-INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (16, 'p', 'group_admin', '/rating', 'GET', '', '', '');
-INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (17, 'p', 'group_anonymous', '/rating', 'GET', '', '', '');
-INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (18, 'p', 'group_user', '/rating', 'POST', '', '', '');
-INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (19, 'p', 'group_admin', '/admin/project/user/:userid', 'GET', '', '', '');
-INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (21, 'p', 'group_user', '/project', 'POST', '', '', '');
-INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (33, 'g', 'group_admin', 'group_anonymous', '', '', '', '');
-INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (34, 'g', 'group_user', 'group_anonymous', '', '', '', '');
-INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (37, 'p', 'group_user', '/project/:id', 'GET', '', '', '');
-INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (36, 'p', 'group_user', '/project/:id/compose', 'GET', '', '', '');
-INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (38, 'p', 'group_user', '/project/:id/deploy', 'POST', '', '', '');
-INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (39, 'p', 'group_user', '/project', 'GET', '', '', '');
-INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (40, 'g', 'user', 'group_user', '', '', '', '');
-INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (41, 'p', 'group_user', '/project/:id/deploy/:cloud_id', 'POST', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (1, 'g', 'anonym', 'group_anonymous', '', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (2, 'g', 'group_admin', 'group_anonymous', '', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (3, 'g', 'group_user', 'group_anonymous', '', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (4, 'g', 'user', 'group_user', '', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (5, 'g', 'admin_petru', 'group_admin', '', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (6, 'g', 'user_petru', 'group_user', '', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (7, 'p', 'group_anonymous', '/health_check', 'GET', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (8, 'p', 'group_anonymous', '/rating/:id', 'GET', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (9, 'p', 'group_anonymous', '/rating', 'GET', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (10, 'p', 'group_admin', '/client', 'POST', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (11, 'p', 'group_admin', '/rating', 'GET', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (12, 'p', 'group_admin', '/admin/client/:id/disable', 'PUT', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (13, 'p', 'group_admin', '/admin/client/:id/enable', 'PUT', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (14, 'p', 'group_admin', '/admin/client/:id', 'PUT', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (15, 'p', 'group_admin', '/admin/project/user/:userid', 'GET', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (16, 'p', 'group_admin', '/rating/:id', 'GET', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (17, 'p', 'group_user', '/client/:id/enable', 'PUT', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (18, 'p', 'group_user', '/client/:id', 'PUT', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (19, 'p', 'group_user', '/client/:id/disable', 'PUT', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (20, 'p', 'group_user', '/rating/:id', 'GET', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (21, 'p', 'group_user', '/rating', 'GET', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (22, 'p', 'group_user', '/rating', 'POST', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (23, 'p', 'group_user', '/project', 'GET', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (24, 'p', 'group_user', '/project', 'POST', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (25, 'p', 'group_user', '/project/:id', 'GET', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (26, 'p', 'group_user', '/project/:id', 'POST', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (27, 'p', 'group_user', '/project/:id', 'PUT', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (28, 'p', 'group_user', '/project/:id', 'DELETE', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (29, 'p', 'group_user', '/project/:id/compose', 'GET', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (30, 'p', 'group_user', '/project/:id/compose', 'POST', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (31, 'p', 'group_user', '/project/:id/deploy', 'POST', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (32, 'p', 'group_user', '/project/:id/deploy/:cloud_id', 'POST', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (33, 'p', 'group_user', '/server', 'GET', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (34, 'p', 'group_user', '/server', 'POST', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (35, 'p', 'group_user', '/server/:id', 'GET', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (36, 'p', 'group_user', '/server/:id', 'PUT', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (37, 'p', 'group_user', '/cloud', 'GET', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (38, 'p', 'group_user', '/cloud', 'POST', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (39, 'p', 'group_user', '/cloud/:id', 'GET', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (40, 'p', 'group_user', '/cloud/:id', 'PUT', '', '', '');
+INSERT INTO public.casbin_rule (id, ptype, v0, v1, v2, v3, v4, v5) VALUES (41, 'p', 'group_user', '/cloud/:id', 'DELETE', '', '', '');
 
diff --git a/migrations/20240401184313_remove_project_id_from_cloud.down.sql b/migrations/20240401184313_remove_project_id_from_cloud.down.sql
new file mode 100644
index 00000000..3b99d4c9
--- /dev/null
+++ b/migrations/20240401184313_remove_project_id_from_cloud.down.sql
@@ -0,0 +1,2 @@
+-- Add down migration script here
+ALTER table cloud ADD COLUMN project_id INT CONSTRAINT cloud_project_id REFERENCES project(id) ON UPDATE CASCADE ON DELETE CASCADE;
diff --git a/migrations/20240401184313_remove_project_id_from_cloud.up.sql b/migrations/20240401184313_remove_project_id_from_cloud.up.sql
new file mode 100644
index 00000000..4974d954
--- /dev/null
+++ b/migrations/20240401184313_remove_project_id_from_cloud.up.sql
@@ -0,0 +1,3 @@
+-- Add up migration script here
+
+alter table cloud DROP column project_id;
diff --git a/src/console/commands/appclient/new.rs b/src/console/commands/appclient/new.rs
index accbd765..dfafa9f0 100644
--- a/src/console/commands/appclient/new.rs
+++ b/src/console/commands/appclient/new.rs
@@ -24,7 +24,7 @@ impl crate::console::commands::CallableTrait for NewCommand {
             let settings = web::Data::new(settings);
             let db_pool = web::Data::new(db_pool);
 
-            //todo get user from trydirect
+            //todo get user from TryDirect
             let user = crate::models::user::User {
                 id: format!("{}", self.user_id),
                 first_name: "first_name".to_string(),
diff --git a/src/db/cloud.rs b/src/db/cloud.rs
index 77c840e5..92f79d1e 100644
--- a/src/db/cloud.rs
+++ b/src/db/cloud.rs
@@ -48,7 +48,6 @@ pub async fn insert(pool: &PgPool, mut cloud: models::Cloud) -> Result<models::C
         r#"
         INSERT INTO cloud (
         user_id,
-        project_id,
         provider,
         cloud_token,
         cloud_key,
@@ -57,11 +56,10 @@ pub async fn insert(pool: &PgPool, mut cloud: models::Cloud) -> Result<models::C
         created_at,
         updated_at
         )
-        VALUES ($1, $2, $3, $4, $5, $6, $7, NOW() at time zone 'utc', NOW() at time zone 'utc')
+        VALUES ($1, $2, $3, $4, $5, $6, NOW() at time zone 'utc', NOW() at time zone 'utc')
         RETURNING id;
         "#,
         cloud.user_id,
-        cloud.project_id,
         cloud.provider,
         cloud.cloud_token,
         cloud.cloud_key,
@@ -89,19 +87,17 @@ pub async fn update(pool: &PgPool, mut cloud: models::Cloud) -> Result<models::C
         UPDATE cloud
         SET
             user_id=$2,
-            project_id=$3,
-            provider=$4,
-            cloud_token=$5,
-            cloud_key=$6,
-            cloud_secret=$7,
-            save_token=$8,
+            provider=$3,
+            cloud_token=$4,
+            cloud_key=$5,
+            cloud_secret=$6,
+            save_token=$7,
             updated_at=NOW() at time zone 'utc'
         WHERE id = $1
         RETURNING *
         "#,
         cloud.id,
         cloud.user_id,
-        cloud.project_id,
         cloud.provider,
         cloud.cloud_token,
         cloud.cloud_key,
diff --git a/src/forms/cloud.rs b/src/forms/cloud.rs
index 704de558..cc54f816 100644
--- a/src/forms/cloud.rs
+++ b/src/forms/cloud.rs
@@ -13,6 +13,7 @@ fn hide_parts(value: String) -> String {
 #[derive(Default, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct CloudForm {
     pub user_id: Option<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
     pub project_id: Option<i32>,
     #[validate(min_length = 2)]
     #[validate(max_length = 50)]
@@ -63,7 +64,7 @@ impl CloudForm {
 
         let mut secret = Secret::new();
         secret.user_id = cloud.user_id.clone();
-        secret.project_id = cloud.project_id.clone().unwrap();
+        secret.provider = cloud.provider.clone();
         cloud.cloud_token = CloudForm::decrypt_field(&mut secret, "cloud_token", cloud.cloud_token.clone(), reveal);
         cloud.cloud_secret = CloudForm::decrypt_field(&mut secret, "cloud_secret", cloud.cloud_secret.clone(), reveal);
         cloud.cloud_key = CloudForm::decrypt_field(&mut secret, "cloud_key", cloud.cloud_key.clone(), reveal);
@@ -126,12 +127,11 @@ impl Into<models::Cloud> for &CloudForm {
         let mut cloud = models::Cloud::default();
         cloud.provider = self.provider.clone();
         cloud.user_id = self.user_id.clone().unwrap();
-        cloud.project_id = self.project_id;
 
         if Some(true) == self.save_token {
             let mut secret = Secret::new();
             secret.user_id = self.user_id.clone().unwrap();
-            secret.project_id = self.project_id.unwrap();
+            secret.provider = self.provider.clone();
 
             cloud.cloud_token = encrypt_field(&mut secret, "cloud_token", self.cloud_token.clone());
             cloud.cloud_key = encrypt_field(&mut secret, "cloud_key", self.cloud_key.clone());
@@ -155,13 +155,12 @@ impl Into<CloudForm> for models::Cloud {
     #[tracing::instrument(name = "Into<CloudForm> for models::Cloud .")]
     fn into(self) -> CloudForm {
         let mut form = CloudForm::default();
-        form.project_id = self.project_id;
-        form.provider = self.provider;
+        form.provider = self.provider.clone();
 
         if Some(true) == self.save_token {
             let mut secret = Secret::new();
             secret.user_id = self.user_id.clone();
-            secret.project_id = self.project_id.unwrap();
+            secret.provider = self.provider;
             secret.field = "cloud_token".to_string();
 
             let value = match self.cloud_token {
diff --git a/src/forms/project/deploy.rs b/src/forms/project/deploy.rs
index 86c1453f..e300a18c 100644
--- a/src/forms/project/deploy.rs
+++ b/src/forms/project/deploy.rs
@@ -2,14 +2,14 @@ use serde_derive::{Deserialize, Serialize};
 use serde_json::Value;
 use serde_valid::Validate;
 use crate::forms;
-use crate::forms::{CloudForm, Server};
+use crate::forms::{CloudForm, ServerForm};
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct Deploy {
     #[validate]
     pub(crate) stack: Stack,
     #[validate]
-    pub(crate) server: Server,
+    pub(crate) server: ServerForm,
     #[validate]
     pub(crate) cloud: CloudForm,
 }
diff --git a/src/forms/project/payload.rs b/src/forms/project/payload.rs
index 5eb115a5..6a2c8686 100644
--- a/src/forms/project/payload.rs
+++ b/src/forms/project/payload.rs
@@ -14,7 +14,7 @@ pub struct Payload {
     #[serde(flatten)]
     pub cloud: Option<forms::CloudForm>,
     #[serde(flatten)]
-    pub server: Option<forms::Server>,
+    pub server: Option<forms::ServerForm>,
     #[serde(flatten)]
     pub stack: forms::project::Stack,
     pub custom: forms::project::Custom,
diff --git a/src/forms/server.rs b/src/forms/server.rs
index 8b78c14e..134973ac 100644
--- a/src/forms/server.rs
+++ b/src/forms/server.rs
@@ -4,7 +4,7 @@ use serde_valid::Validate;
 use chrono::{Utc};
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
-pub struct Server {
+pub struct ServerForm {
     // pub cloud_id: i32,
     // pub project_id: i32,
     pub region: String,
@@ -14,7 +14,7 @@ pub struct Server {
     pub disk_type: Option<String>,
 }
 
-impl Into<models::Server> for &Server {
+impl Into<models::Server> for &ServerForm {
     fn into(self) -> models::Server {
         let mut server = models::Server::default();
         server.disk_type = self.disk_type.clone();
@@ -29,22 +29,10 @@ impl Into<models::Server> for &Server {
     }
 }
 
-// impl From<Server> for models::Server {
-//     fn from(self, value: Server) -> &mut Self {
-//         self.disk_type = value.disk_type.clone();
-//         self.region = value.region.clone();
-//         self.server = value.server.clone();
-//         self.zone = value.zone.clone();
-//         self.os = value.os.clone();
-//         self.updated_at = Utc::now();
-//         self
-//     }
-// }
+impl Into<ServerForm> for models::Server {
 
-impl Into<Server> for models::Server {
-
-    fn into(self) -> Server {
-        let mut form = Server::default();
+    fn into(self) -> ServerForm {
+        let mut form = ServerForm::default();
         // form.cloud_id = self.cloud_id;
         // form.project_id = self.project_id;
         form.disk_type = self.disk_type;
diff --git a/src/helpers/cloud/security.rs b/src/helpers/cloud/security.rs
index 2e389940..23e82cce 100644
--- a/src/helpers/cloud/security.rs
+++ b/src/helpers/cloud/security.rs
@@ -9,7 +9,7 @@ use tracing::Instrument;
 #[derive(Debug, Default, PartialEq, Clone)]
 pub(crate) struct Secret {
     pub(crate) user_id: String,
-    pub(crate) project_id: i32,
+    pub(crate) provider: String,
     pub(crate) field: String, // cloud_token/cloud_key/cloud_secret
     pub(crate) nonce: Vec<u8>,
 }
@@ -20,7 +20,7 @@ impl Secret {
 
         Secret {
             user_id: "".to_string(),
-            project_id: 0,
+            provider: "".to_string(),
             field: "".to_string(),
             nonce: vec![],
         }
@@ -45,7 +45,7 @@ impl Secret {
     #[tracing::instrument(name = "Secret::save")]
     fn save(&self, value: &[u8]) -> &Self {
         let mut conn = Secret::connect_storage();
-        let key = format!("{}_{}_{}", self.user_id, self.project_id, self.field);
+        let key = format!("{}_{}_{}", self.user_id, self.provider, self.field);
         tracing::debug!("Saving into storage..");
         let _: () = match conn.set(key, value) {
             Ok(s) => s,
@@ -118,7 +118,7 @@ impl Secret {
             .clone();
         let key: &Key::<Aes256Gcm> = Key::<Aes256Gcm>::from_slice(&sec_key.as_bytes());
         // eprintln!("decrypt: Key str {key:?}");
-        let rkey = format!("{}_{}_{}", self.user_id, self.project_id, self.field);
+        let rkey = format!("{}_{}_{}", self.user_id, self.provider, self.field);
         eprintln!("decrypt: Key str {rkey:?}");
         self.get(rkey);
         // eprintln!("decrypt: nonce b64:decoded {nonce:?}");
diff --git a/src/middleware/authentication/method/f_oauth.rs b/src/middleware/authentication/method/f_oauth.rs
index cec47b4c..1b861a75 100644
--- a/src/middleware/authentication/method/f_oauth.rs
+++ b/src/middleware/authentication/method/f_oauth.rs
@@ -35,6 +35,7 @@ pub async fn try_oauth(req: &mut ServiceRequest) -> Result<bool, String> {
         .map_err(|err| format!("{err}"))?;
 
     // control access using user role
+    tracing::debug!("ACL check for role: {}", user.role.clone());
     let acl_vals = actix_casbin_auth::CasbinVals {
         subject: user.role.clone(),
         domain: None,
diff --git a/src/models/cloud.rs b/src/models/cloud.rs
index 77c3224a..b7c8f636 100644
--- a/src/models/cloud.rs
+++ b/src/models/cloud.rs
@@ -5,7 +5,6 @@ use serde_derive::{Deserialize, Serialize};
 pub struct Cloud {
     pub id: i32,
     pub user_id: String,
-    pub project_id: Option<i32>,
     pub provider: String,
     pub cloud_token: Option<String>,
     pub cloud_key: Option<String>,
@@ -28,19 +27,17 @@ impl std::fmt::Display for Cloud {
         let cloud_token = mask_string(self.cloud_token.as_ref());
         let cloud_secret = mask_string(self.cloud_secret.as_ref());
 
-        write!(f, "{} cloud creds: cloud_key : {} cloud_token: {} cloud_secret: {} project_id: {:?}",
+        write!(f, "{} cloud creds: cloud_key : {} cloud_token: {} cloud_secret: {}",
                self.provider,
                cloud_key,
                cloud_token,
                cloud_secret,
-               self.project_id
         )
     }
 }
 
 impl Cloud {
     pub fn new(user_id: String,
-               project_id: Option<i32>,
                provider: String,
                cloud_token: Option<String>,
                cloud_key: Option<String>,
@@ -50,7 +47,6 @@ impl Cloud {
         Self {
             id: 0,
             user_id,
-            project_id,
             provider,
             cloud_token,
             cloud_key,
@@ -68,7 +64,6 @@ impl Default for Cloud {
             id: 0,
             provider: "".to_string(),
             user_id: "".to_string(),
-            project_id: Default::default(),
             cloud_key: Default::default(),
             cloud_token: Default::default(),
             cloud_secret: Default::default(),
diff --git a/src/routes/project/deploy.rs b/src/routes/project/deploy.rs
index 661115e0..95105173 100644
--- a/src/routes/project/deploy.rs
+++ b/src/routes/project/deploy.rs
@@ -168,7 +168,9 @@ pub async fn saved_item(
     let cloud = match db::cloud::fetch(pg_pool.get_ref(), cloud_id).await {
         Ok(cloud) => {
             match cloud {
-                Some(cloud) => cloud,
+                Some(mut cloud) => {
+                    cloud
+                },
                 None => {
                     return Err(JsonResponse::<models::Project>::build().not_found("No cloud configured"));
                 }
diff --git a/src/routes/server/update.rs b/src/routes/server/update.rs
index b0741259..52f03273 100644
--- a/src/routes/server/update.rs
+++ b/src/routes/server/update.rs
@@ -13,7 +13,7 @@ use std::ops::Deref;
 #[put("/{id}")]
 pub async fn item(
     path: web::Path<(i32,)>,
-    form: web::Json<forms::server::Server>,
+    form: web::Json<forms::server::ServerForm>,
     user: web::ReqData<Arc<models::User>>,
     pg_pool: Data<PgPool>,
 ) -> Result<impl Responder> {

From 0576773da4b209563289637434df5d97a908f956 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Tue, 2 Apr 2024 15:59:58 +0300
Subject: [PATCH 251/284] fix warnings, return app id from docker image
 validator

---
 src/console/commands/debug/json.rs |  3 +--
 src/forms/cloud.rs                 |  2 +-
 src/forms/project/form.rs          | 29 ++++++++++++++++++++++++-----
 src/helpers/cloud/mod.rs           |  2 +-
 src/helpers/cloud/security.rs      |  2 +-
 src/helpers/compressor.rs          |  4 ++--
 src/helpers/json.rs                | 22 +++++++++++-----------
 src/helpers/mq_manager.rs          |  2 +-
 src/routes/client/disable.rs       |  4 ++--
 src/routes/client/enable.rs        |  4 ++--
 src/routes/client/update.rs        |  4 ++--
 src/routes/cloud/update.rs         |  2 --
 src/routes/project/deploy.rs       |  4 ++--
 src/routes/project/update.rs       | 12 ++++++++----
 src/startup.rs                     |  6 +-----
 15 files changed, 59 insertions(+), 43 deletions(-)

diff --git a/src/console/commands/debug/json.rs b/src/console/commands/debug/json.rs
index 047faf0f..e05e3b07 100644
--- a/src/console/commands/debug/json.rs
+++ b/src/console/commands/debug/json.rs
@@ -1,5 +1,4 @@
-use crate::configuration::get_configuration;
-use actix_web::{rt, post, web, HttpResponse, Result, http::header::ContentType};
+use actix_web::{Result};
 
 pub struct JsonCommand {
     line: usize,
diff --git a/src/forms/cloud.rs b/src/forms/cloud.rs
index cc54f816..fe4cdf6a 100644
--- a/src/forms/cloud.rs
+++ b/src/forms/cloud.rs
@@ -1,5 +1,5 @@
 use crate::models;
-use serde::{Deserialize, Serialize, Serializer};
+use serde::{Deserialize, Serialize};
 use serde_valid::Validate;
 use crate::helpers::cloud::security::Secret;
 use tracing::Instrument;
diff --git a/src/forms/project/form.rs b/src/forms/project/form.rs
index a0514a4f..b849abb5 100644
--- a/src/forms/project/form.rs
+++ b/src/forms/project/form.rs
@@ -18,18 +18,31 @@ impl TryFrom<&models::Project> for ProjectForm {
     }
 }
 
+
+#[derive(Serialize, Default)]
+pub struct DockerImageReadResult {
+   pub(crate) id: String,
+   pub(crate) readable: bool
+}
+
 impl ProjectForm {
-    pub async fn is_readable_docker_image(&self) -> Result<bool, String> {
+    pub async fn is_readable_docker_image(&self) -> Result<DockerImageReadResult, String> {
         for app in &self.custom.web {
             if !app.app.docker_image.is_active().await? {
-                return Ok(false);
+                return Ok(DockerImageReadResult{
+                    id: app.app.id.clone(),
+                    readable: false
+                });
             }
         }
 
         if let Some(service) = &self.custom.service {
             for app in service {
                 if !app.app.docker_image.is_active().await? {
-                    return Ok(false);
+                    return Ok(DockerImageReadResult{
+                        id: app.app.id.clone(),
+                        readable: false
+                    });
                 }
             }
         }
@@ -37,10 +50,16 @@ impl ProjectForm {
         if let Some(features) = &self.custom.feature {
             for app in features {
                 if !app.app.docker_image.is_active().await? {
-                    return Ok(false);
+                    return Ok(DockerImageReadResult{
+                        id: app.app.id.clone(),
+                        readable: false
+                    });
                 }
             }
         }
-        Ok(true)
+        Ok(DockerImageReadResult{
+            id: "".to_owned(),
+            readable: true
+        })
     }
 }
\ No newline at end of file
diff --git a/src/helpers/cloud/mod.rs b/src/helpers/cloud/mod.rs
index 9d2c9991..96224c8b 100644
--- a/src/helpers/cloud/mod.rs
+++ b/src/helpers/cloud/mod.rs
@@ -1,2 +1,2 @@
 pub(crate) mod security;
-pub use security::*;
\ No newline at end of file
+pub use security::Secret;
\ No newline at end of file
diff --git a/src/helpers/cloud/security.rs b/src/helpers/cloud/security.rs
index 23e82cce..ddb6d1bb 100644
--- a/src/helpers/cloud/security.rs
+++ b/src/helpers/cloud/security.rs
@@ -7,7 +7,7 @@ use redis::{Commands, Connection};
 use tracing::Instrument;
 
 #[derive(Debug, Default, PartialEq, Clone)]
-pub(crate) struct Secret {
+pub struct Secret {
     pub(crate) user_id: String,
     pub(crate) provider: String,
     pub(crate) field: String, // cloud_token/cloud_key/cloud_secret
diff --git a/src/helpers/compressor.rs b/src/helpers/compressor.rs
index c6e12589..ec126fca 100644
--- a/src/helpers/compressor.rs
+++ b/src/helpers/compressor.rs
@@ -1,5 +1,5 @@
-use brotli::{CompressorWriter, Decompressor};
-use std::io::{Read, Write};
+use brotli::{CompressorWriter};
+use std::io::{Write};
 
 pub fn compress(input: &str) -> Vec<u8> {
     let mut compressed = Vec::new();
diff --git a/src/helpers/json.rs b/src/helpers/json.rs
index 36f8d39f..ebb9df1e 100644
--- a/src/helpers/json.rs
+++ b/src/helpers/json.rs
@@ -2,7 +2,6 @@ use actix_web::error::{ErrorBadRequest, ErrorConflict, ErrorInternalServerError,
 use actix_web::web::Json;
 use actix_web::Error;
 use serde_derive::Serialize;
-use std::convert::From;
 
 #[derive(Serialize)]
 pub(crate) struct JsonResponse<T> {
@@ -90,16 +89,17 @@ where
         ErrorInternalServerError(self.set_msg(msg).to_string())
     }
 
-    pub(crate) fn unauthorized<I: Into<String>>(
-        self,
-        msg: I,
-    ) -> Error {
-        ErrorUnauthorized(self.set_msg(msg).to_string())
-    }
- 
-    pub(crate) fn conflict<I: Into<String>>(self, msg: I) -> Error {
-        ErrorConflict(self.set_msg(msg).to_string())
-    }
+    // not used
+    // pub(crate) fn unauthorized<I: Into<String>>(
+    //     self,
+    //     msg: I,
+    // ) -> Error {
+    //     ErrorUnauthorized(self.set_msg(msg).to_string())
+    // }
+    //
+    // pub(crate) fn conflict<I: Into<String>>(self, msg: I) -> Error {
+    //     ErrorConflict(self.set_msg(msg).to_string())
+    // }
 }
 
 impl<T> JsonResponse<T>
diff --git a/src/helpers/mq_manager.rs b/src/helpers/mq_manager.rs
index dfcda23d..f38604a0 100644
--- a/src/helpers/mq_manager.rs
+++ b/src/helpers/mq_manager.rs
@@ -127,7 +127,7 @@ impl MqManager {
         let mut args = FieldTable::default();
         args.insert("x-expires".into(), AMQPValue::LongUInt(3600000));
 
-        let queue = channel.queue_declare(
+        let _queue = channel.queue_declare(
             queue_name,
             QueueDeclareOptions {
                 passive: false,
diff --git a/src/routes/client/disable.rs b/src/routes/client/disable.rs
index 0d1fe3be..1c8b9d17 100644
--- a/src/routes/client/disable.rs
+++ b/src/routes/client/disable.rs
@@ -15,7 +15,7 @@ pub async fn disable_handler(
     path: web::Path<(i32,)>,
 ) -> Result<impl Responder> {
     let client_id = path.0;
-    let mut client = db::client::fetch(pg_pool.get_ref(), client_id)
+    let client = db::client::fetch(pg_pool.get_ref(), client_id)
         .await
         .map_err(|msg| JsonResponse::<models::Client>::build().internal_server_error(msg))
         .and_then( |client| {
@@ -38,7 +38,7 @@ pub async fn admin_disable_handler(
     path: web::Path<(i32,)>,
 ) -> Result<impl Responder> {
     let client_id = path.0;
-    let mut client = db::client::fetch(pg_pool.get_ref(), client_id)
+    let client = db::client::fetch(pg_pool.get_ref(), client_id)
         .await
         .map_err(|msg| JsonResponse::<models::Client>::build().internal_server_error(msg))?
         .ok_or_else(|| JsonResponse::<models::Client>::build().not_found("not found"))?;
diff --git a/src/routes/client/enable.rs b/src/routes/client/enable.rs
index e3d6c43e..e3955a6d 100644
--- a/src/routes/client/enable.rs
+++ b/src/routes/client/enable.rs
@@ -16,7 +16,7 @@ pub async fn enable_handler(
     path: web::Path<(i32,)>,
 ) -> Result<impl Responder> {
     let client_id = path.0;
-    let mut client = db::client::fetch(pg_pool.get_ref(), client_id)
+    let client = db::client::fetch(pg_pool.get_ref(), client_id)
         .await
         .map_err(|msg| JsonResponse::<models::Client>::build().internal_server_error(msg))?
         .ok_or_else(|| JsonResponse::<models::Client>::build().not_found("not found"))?;
@@ -37,7 +37,7 @@ pub async fn admin_enable_handler(
     path: web::Path<(i32,)>,
 ) -> Result<impl Responder> {
     let client_id = path.0;
-    let mut client = db::client::fetch(pg_pool.get_ref(), client_id)
+    let client = db::client::fetch(pg_pool.get_ref(), client_id)
         .await
         .map_err(|msg| JsonResponse::<models::Client>::build().internal_server_error(msg))?
         .ok_or_else(|| JsonResponse::<models::Client>::build().not_found("not found"))?;
diff --git a/src/routes/client/update.rs b/src/routes/client/update.rs
index 3ed728bf..de095814 100644
--- a/src/routes/client/update.rs
+++ b/src/routes/client/update.rs
@@ -15,7 +15,7 @@ pub async fn update_handler(
     path: web::Path<(i32,)>,
 ) -> Result<impl Responder> {
     let client_id = path.0;
-    let mut client = db::client::fetch(pg_pool.get_ref(), client_id)
+    let client = db::client::fetch(pg_pool.get_ref(), client_id)
         .await
         .map_err(|msg| JsonResponse::<models::Client>::build().internal_server_error(msg))?
         .ok_or_else(|| JsonResponse::<models::Client>::build().not_found("not found"))?;
@@ -36,7 +36,7 @@ pub async fn admin_update_handler(
     path: web::Path<(i32,)>,
 ) -> Result<impl Responder> {
     let client_id = path.0;
-    let mut client = db::client::fetch(pg_pool.get_ref(), client_id)
+    let client = db::client::fetch(pg_pool.get_ref(), client_id)
         .await
         .map_err(|msg| JsonResponse::<models::Client>::build().internal_server_error(msg))?
         .ok_or_else(|| JsonResponse::<models::Client>::build().not_found("not found"))?;
diff --git a/src/routes/cloud/update.rs b/src/routes/cloud/update.rs
index 7672f551..5b4f4a1b 100644
--- a/src/routes/cloud/update.rs
+++ b/src/routes/cloud/update.rs
@@ -8,7 +8,6 @@ use sqlx::PgPool;
 use std::sync::Arc;
 use tracing::Instrument;
 use std::ops::Deref;
-use chrono::Utc;
 
 #[tracing::instrument(name = "Update cloud.")]
 #[put("/{id}")]
@@ -38,7 +37,6 @@ pub async fn item(
     let mut cloud:models::Cloud = form.deref().into();
     cloud.id = cloud_row.id;
     cloud.user_id = user.id.clone();
-    // cloud.updated_at = Utc::now();
 
     tracing::debug!("Updating cloud {:?}", cloud);
 
diff --git a/src/routes/project/deploy.rs b/src/routes/project/deploy.rs
index 95105173..b0d767e5 100644
--- a/src/routes/project/deploy.rs
+++ b/src/routes/project/deploy.rs
@@ -130,7 +130,7 @@ pub async fn item(
 #[post("/{id}/deploy/{cloud_id}")]
 pub async fn saved_item(
     user: web::ReqData<Arc<models::User>>,
-    mut form: web::Json<forms::project::Deploy>,
+    form: web::Json<forms::project::Deploy>,
     path: web::Path<(i32, i32)>,
     pg_pool: Data<PgPool>,
     mq_manager: Data<MqManager>,
@@ -168,7 +168,7 @@ pub async fn saved_item(
     let cloud = match db::cloud::fetch(pg_pool.get_ref(), cloud_id).await {
         Ok(cloud) => {
             match cloud {
-                Some(mut cloud) => {
+                Some(cloud) => {
                     cloud
                 },
                 None => {
diff --git a/src/routes/project/update.rs b/src/routes/project/update.rs
index cdd532e6..a6a8a8c9 100644
--- a/src/routes/project/update.rs
+++ b/src/routes/project/update.rs
@@ -1,5 +1,5 @@
 use std::str::FromStr;
-use crate::forms::project::ProjectForm;
+use crate::forms::project::{ProjectForm, DockerImageReadResult};
 use crate::helpers::JsonResponse;
 use crate::models;
 use crate::db;
@@ -41,11 +41,15 @@ pub async fn item(
 
     let project_name = form.custom.custom_stack_code.clone();
 
-    if Ok(false) == form.is_readable_docker_image().await {
-        return Err(JsonResponse::<models::Project>::build()
-            .bad_request("Can not access docker image"));
+    if let Ok(result) = form.is_readable_docker_image().await {
+        if false == result.readable {
+            return Err(JsonResponse::<DockerImageReadResult>::build()
+                .set_item(result)
+                .bad_request("Can not access docker image"));
+        }
     }
 
+
     let body: Value = serde_json::to_value::<ProjectForm>(form)
         .or(serde_json::to_value::<ProjectForm>(ProjectForm::default()))
         .unwrap();
diff --git a/src/startup.rs b/src/startup.rs
index 302070d0..741393f1 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -9,15 +9,11 @@ use actix_web::{
     web,
     App,
     HttpServer,
-    HttpResponse,
-    FromRequest,
-    rt,
 };
 use crate::middleware;
 use sqlx::{Pool, Postgres};
 use std::net::TcpListener;
 use tracing_actix_web::TracingLogger;
-use redis::Commands;
 
 pub async fn run(
     listener: TcpListener,
@@ -32,7 +28,7 @@ pub async fn run(
 
     let authorization = middleware::authorization::try_new(settings.database.connection_string()).await?;
     let json_config = web::JsonConfig::default()
-        .error_handler(|err, req| { //todo
+        .error_handler(|err, _req| { //todo
             let msg: String = match err {
                  error::JsonPayloadError::Deserialize(err) => format!("{{\"kind\":\"deserialize\",\"line\":{}, \"column\":{}, \"msg\":\"{}\"}}", err.line(), err.column(), err),
                  _ => format!("{{\"kind\":\"other\",\"msg\":\"{}\"}}", err)

From 28a8e5877c16a39e3758fc8f3fb3783589df2523 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Tue, 2 Apr 2024 21:57:16 +0300
Subject: [PATCH 252/284] casbin-debug explain features

---
 Cargo.lock                           | 661 +++++++++++++++------------
 Cargo.toml                           |   1 +
 src/console/commands/debug/casbin.rs |   2 +-
 3 files changed, 378 insertions(+), 286 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index c849d152..d93e7a9b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4,8 +4,8 @@ version = 3
 
 [[package]]
 name = "actix-casbin-auth"
-version = "0.4.4"
-source = "git+https://github.com/smart--petea/actix-casbin-auth.git?branch=dirty-master#5b76cc4e0df68ccee02c563b947a788ab1cb5193"
+version = "1.0.0"
+source = "git+https://github.com/smart--petea/actix-casbin-auth.git?branch=dirty-master#2e0c3ae76fa27a3bd515478317e487082b8e8294"
 dependencies = [
  "actix-service",
  "actix-web",
@@ -20,7 +20,7 @@ version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a"
 dependencies = [
- "bitflags 2.4.2",
+ "bitflags 2.5.0",
  "bytes",
  "futures-core",
  "futures-sink",
@@ -56,9 +56,9 @@ dependencies = [
  "actix-rt",
  "actix-service",
  "actix-utils",
- "ahash 0.8.10",
+ "ahash 0.8.11",
  "base64 0.21.7",
- "bitflags 2.4.2",
+ "bitflags 2.5.0",
  "brotli",
  "bytes",
  "bytestring",
@@ -92,7 +92,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb"
 dependencies = [
  "quote",
- "syn 2.0.51",
+ "syn 2.0.57",
 ]
 
 [[package]]
@@ -171,7 +171,7 @@ dependencies = [
  "actix-service",
  "actix-utils",
  "actix-web-codegen",
- "ahash 0.8.10",
+ "ahash 0.8.11",
  "bytes",
  "bytestring",
  "cfg-if",
@@ -205,7 +205,7 @@ dependencies = [
  "actix-router",
  "proc-macro2",
  "quote",
- "syn 2.0.51",
+ "syn 2.0.57",
 ]
 
 [[package]]
@@ -236,9 +236,9 @@ dependencies = [
 
 [[package]]
 name = "ahash"
-version = "0.8.10"
+version = "0.8.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b79b82693f705137f8fb9b37871d99e4f9a7df12b917eed79c3d3954830a60b"
+checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
 dependencies = [
  "cfg-if",
  "const-random",
@@ -250,9 +250,9 @@ dependencies = [
 
 [[package]]
 name = "aho-corasick"
-version = "1.1.2"
+version = "1.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
 dependencies = [
  "memchr",
 ]
@@ -391,9 +391,9 @@ dependencies = [
 
 [[package]]
 name = "anyhow"
-version = "1.0.80"
+version = "1.0.81"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1"
+checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
 
 [[package]]
 name = "assert-json-diff"
@@ -423,23 +423,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3"
 dependencies = [
  "concurrent-queue",
- "event-listener 5.1.0",
- "event-listener-strategy 0.5.0",
+ "event-listener 5.2.0",
+ "event-listener-strategy 0.5.1",
  "futures-core",
  "pin-project-lite",
 ]
 
 [[package]]
 name = "async-executor"
-version = "1.8.0"
+version = "1.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c"
+checksum = "10b3e585719c2358d2660232671ca8ca4ddb4be4ce8a1842d6c2dc8685303316"
 dependencies = [
  "async-lock 3.3.0",
  "async-task",
  "concurrent-queue",
- "fastrand 2.0.1",
- "futures-lite 2.2.0",
+ "fastrand 2.0.2",
+ "futures-lite 2.3.0",
  "slab",
 ]
 
@@ -451,10 +451,10 @@ checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c"
 dependencies = [
  "async-channel 2.2.0",
  "async-executor",
- "async-io 2.3.1",
+ "async-io 2.3.2",
  "async-lock 3.3.0",
  "blocking",
- "futures-lite 2.2.0",
+ "futures-lite 2.3.0",
  "once_cell",
 ]
 
@@ -491,18 +491,18 @@ dependencies = [
 
 [[package]]
 name = "async-io"
-version = "2.3.1"
+version = "2.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f97ab0c5b00a7cdbe5a371b9a782ee7be1316095885c8a4ea1daf490eb0ef65"
+checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884"
 dependencies = [
  "async-lock 3.3.0",
  "cfg-if",
  "concurrent-queue",
  "futures-io",
- "futures-lite 2.2.0",
+ "futures-lite 2.3.0",
  "parking",
- "polling 3.5.0",
- "rustix 0.38.31",
+ "polling 3.6.0",
+ "rustix 0.38.32",
  "slab",
  "tracing",
  "windows-sys 0.52.0",
@@ -548,13 +548,13 @@ checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799"
 
 [[package]]
 name = "async-trait"
-version = "0.1.77"
+version = "0.1.79"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
+checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.51",
+ "syn 2.0.57",
 ]
 
 [[package]]
@@ -581,27 +581,17 @@ version = "1.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
 
-[[package]]
-name = "atomic-write-file"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edcdbedc2236483ab103a53415653d6b4442ea6141baf1ffa85df29635e88436"
-dependencies = [
- "nix",
- "rand 0.8.5",
-]
-
 [[package]]
 name = "autocfg"
-version = "1.1.0"
+version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
 
 [[package]]
 name = "backtrace"
-version = "0.3.69"
+version = "0.3.71"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
+checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d"
 dependencies = [
  "addr2line",
  "cc",
@@ -632,9 +622,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
 [[package]]
 name = "bitflags"
-version = "2.4.2"
+version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
+checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
 
 [[package]]
 name = "block-buffer"
@@ -663,18 +653,18 @@ dependencies = [
  "async-channel 2.2.0",
  "async-lock 3.3.0",
  "async-task",
- "fastrand 2.0.1",
+ "fastrand 2.0.2",
  "futures-io",
- "futures-lite 2.2.0",
+ "futures-lite 2.3.0",
  "piper",
  "tracing",
 ]
 
 [[package]]
 name = "brotli"
-version = "3.4.0"
+version = "3.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f"
+checksum = "d640d25bc63c50fb1f0b545ffd80207d2e10a4c965530809b40ba3386825c391"
 dependencies = [
  "alloc-no-stdlib",
  "alloc-stdlib",
@@ -693,9 +683,9 @@ dependencies = [
 
 [[package]]
 name = "bumpalo"
-version = "3.15.3"
+version = "3.15.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b"
+checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa"
 
 [[package]]
 name = "bytecount"
@@ -711,9 +701,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
 
 [[package]]
 name = "bytes"
-version = "1.5.0"
+version = "1.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
+checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
 
 [[package]]
 name = "bytestring"
@@ -735,9 +725,9 @@ dependencies = [
 
 [[package]]
 name = "cargo-platform"
-version = "0.1.7"
+version = "0.1.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "694c8807f2ae16faecc43dc17d74b3eb042482789fd0eb64b39a2e04e087053f"
+checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc"
 dependencies = [
  "serde",
 ]
@@ -772,6 +762,9 @@ dependencies = [
  "rhai",
  "ritelinked",
  "serde",
+ "slog",
+ "slog-async",
+ "slog-term",
  "thiserror",
  "tokio",
 ]
@@ -787,10 +780,11 @@ dependencies = [
 
 [[package]]
 name = "cc"
-version = "1.0.88"
+version = "1.0.90"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02f341c093d19155a6e41631ce5971aac4e9a868262212153124c15fa22d1cdc"
+checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5"
 dependencies = [
+ "jobserver",
  "libc",
 ]
 
@@ -828,9 +822,9 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "4.5.1"
+version = "4.5.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da"
+checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
 dependencies = [
  "clap_builder",
  "clap_derive",
@@ -838,9 +832,9 @@ dependencies = [
 
 [[package]]
 name = "clap_builder"
-version = "4.5.1"
+version = "4.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb"
+checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
 dependencies = [
  "anstream",
  "anstyle",
@@ -850,14 +844,14 @@ dependencies = [
 
 [[package]]
 name = "clap_derive"
-version = "4.5.0"
+version = "4.5.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47"
+checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
 dependencies = [
- "heck",
+ "heck 0.5.0",
  "proc-macro2",
  "quote",
- "syn 2.0.51",
+ "syn 2.0.57",
 ]
 
 [[package]]
@@ -902,9 +896,9 @@ dependencies = [
 
 [[package]]
 name = "const-random"
-version = "0.1.17"
+version = "0.1.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5aaf16c9c2c612020bcfd042e170f6e32de9b9d75adb5277cdbbd2e2c8c8299a"
+checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
 dependencies = [
  "const-random-macro",
 ]
@@ -939,9 +933,12 @@ dependencies = [
 
 [[package]]
 name = "cookie-factory"
-version = "0.3.2"
+version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b"
+checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2"
+dependencies = [
+ "futures",
+]
 
 [[package]]
 name = "core-foundation"
@@ -994,9 +991,9 @@ dependencies = [
 
 [[package]]
 name = "crossbeam-channel"
-version = "0.5.11"
+version = "0.5.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b"
+checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95"
 dependencies = [
  "crossbeam-utils",
 ]
@@ -1238,6 +1235,16 @@ dependencies = [
  "dirs-sys",
 ]
 
+[[package]]
+name = "dirs-next"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
+dependencies = [
+ "cfg-if",
+ "dirs-sys-next",
+]
+
 [[package]]
 name = "dirs-sys"
 version = "0.3.7"
@@ -1249,6 +1256,17 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "dirs-sys-next"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
 [[package]]
 name = "dlv-list"
 version = "0.3.0"
@@ -1263,12 +1281,12 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
 
 [[package]]
 name = "docker-compose-types"
-version = "0.7.1"
+version = "0.7.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e366950e7785fe0e404901a2b4e5c09ebd6656767f0c2167e34c5068ce0cc2d"
+checksum = "6d6fdd6fa1c9e8e716f5f73406b868929f468702449621e7397066478b9bf89c"
 dependencies = [
  "derive_builder 0.13.1",
- "indexmap 2.2.3",
+ "indexmap 2.2.6",
  "serde",
  "serde_yaml",
 ]
@@ -1358,9 +1376,9 @@ dependencies = [
 
 [[package]]
 name = "event-listener"
-version = "5.1.0"
+version = "5.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7ad6fd685ce13acd6d9541a30f6db6567a7a24c9ffd4ba2955d29e3f22c8b27"
+checksum = "2b5fb89194fa3cad959b833185b3063ba881dbfc7030680b314250779fb4cc91"
 dependencies = [
  "concurrent-queue",
  "parking",
@@ -1379,11 +1397,11 @@ dependencies = [
 
 [[package]]
 name = "event-listener-strategy"
-version = "0.5.0"
+version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291"
+checksum = "332f51cb23d20b0de8458b86580878211da09bcd4503cb579c225b3d124cabb3"
 dependencies = [
- "event-listener 5.1.0",
+ "event-listener 5.2.0",
  "pin-project-lite",
 ]
 
@@ -1407,9 +1425,9 @@ dependencies = [
 
 [[package]]
 name = "fastrand"
-version = "2.0.1"
+version = "2.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
+checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984"
 
 [[package]]
 name = "finl_unicode"
@@ -1571,11 +1589,11 @@ dependencies = [
 
 [[package]]
 name = "futures-lite"
-version = "2.2.0"
+version = "2.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba"
+checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5"
 dependencies = [
- "fastrand 2.0.1",
+ "fastrand 2.0.2",
  "futures-core",
  "futures-io",
  "parking",
@@ -1590,7 +1608,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.51",
+ "syn 2.0.57",
 ]
 
 [[package]]
@@ -1687,9 +1705,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
 
 [[package]]
 name = "h2"
-version = "0.3.24"
+version = "0.3.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9"
+checksum = "4fbd2820c5e49886948654ab546d0688ff24530286bdcf8fca3cefb16d4618eb"
 dependencies = [
  "bytes",
  "fnv",
@@ -1697,7 +1715,7 @@ dependencies = [
  "futures-sink",
  "futures-util",
  "http",
- "indexmap 2.2.3",
+ "indexmap 2.2.6",
  "slab",
  "tokio",
  "tokio-util",
@@ -1728,7 +1746,7 @@ version = "0.14.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
 dependencies = [
- "ahash 0.8.10",
+ "ahash 0.8.11",
  "allocator-api2",
 ]
 
@@ -1750,11 +1768,17 @@ dependencies = [
  "unicode-segmentation",
 ]
 
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
 [[package]]
 name = "hermit-abi"
-version = "0.3.8"
+version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "379dada1584ad501b383485dd706b8afb7a70fcbc7f4da7d780638a5a6124a60"
+checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
 
 [[package]]
 name = "hex"
@@ -1791,9 +1815,9 @@ dependencies = [
 
 [[package]]
 name = "http"
-version = "0.2.11"
+version = "0.2.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb"
+checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
 dependencies = [
  "bytes",
  "fnv",
@@ -1932,9 +1956,9 @@ dependencies = [
 
 [[package]]
 name = "indexmap"
-version = "2.2.3"
+version = "2.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177"
+checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
 dependencies = [
  "equivalent",
  "hashbrown 0.14.3",
@@ -1983,6 +2007,17 @@ version = "2.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
 
+[[package]]
+name = "is-terminal"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "windows-sys 0.52.0",
+]
+
 [[package]]
 name = "itertools"
 version = "0.12.1"
@@ -1994,15 +2029,24 @@ dependencies = [
 
 [[package]]
 name = "itoa"
-version = "1.0.10"
+version = "1.0.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
+checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+
+[[package]]
+name = "jobserver"
+version = "0.1.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6"
+dependencies = [
+ "libc",
+]
 
 [[package]]
 name = "js-sys"
-version = "0.3.68"
+version = "0.3.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee"
+checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
 dependencies = [
  "wasm-bindgen",
 ]
@@ -2061,13 +2105,12 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
 
 [[package]]
 name = "libredox"
-version = "0.0.1"
+version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8"
+checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
 dependencies = [
- "bitflags 2.4.2",
+ "bitflags 2.5.0",
  "libc",
- "redox_syscall 0.4.1",
 ]
 
 [[package]]
@@ -2117,9 +2160,9 @@ dependencies = [
 
 [[package]]
 name = "log"
-version = "0.4.20"
+version = "0.4.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
 
 [[package]]
 name = "matchers"
@@ -2142,9 +2185,9 @@ dependencies = [
 
 [[package]]
 name = "memchr"
-version = "2.7.1"
+version = "2.7.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
+checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
 
 [[package]]
 name = "mime"
@@ -2184,9 +2227,9 @@ dependencies = [
 
 [[package]]
 name = "mio"
-version = "0.8.10"
+version = "0.8.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
+checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
 dependencies = [
  "libc",
  "log",
@@ -2218,17 +2261,6 @@ dependencies = [
  "tempfile",
 ]
 
-[[package]]
-name = "nix"
-version = "0.27.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
-dependencies = [
- "bitflags 2.4.2",
- "cfg-if",
- "libc",
-]
-
 [[package]]
 name = "nom"
 version = "7.1.3"
@@ -2295,7 +2327,7 @@ version = "0.10.64"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f"
 dependencies = [
- "bitflags 2.4.2",
+ "bitflags 2.5.0",
  "cfg-if",
  "foreign-types",
  "libc",
@@ -2312,7 +2344,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.51",
+ "syn 2.0.57",
 ]
 
 [[package]]
@@ -2323,9 +2355,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
 
 [[package]]
 name = "openssl-sys"
-version = "0.9.101"
+version = "0.9.102"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff"
+checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2"
 dependencies = [
  "cc",
  "libc",
@@ -2440,9 +2472,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
 
 [[package]]
 name = "pest"
-version = "2.7.7"
+version = "2.7.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "219c0dcc30b6a27553f9cc242972b67f75b60eb0db71f0b5462f38b058c41546"
+checksum = "311fb059dee1a7b802f036316d790138c613a4e8b180c822e3925a662e9f0c95"
 dependencies = [
  "memchr",
  "thiserror",
@@ -2451,9 +2483,9 @@ dependencies = [
 
 [[package]]
 name = "pest_derive"
-version = "2.7.7"
+version = "2.7.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22e1288dbd7786462961e69bfd4df7848c1e37e8b74303dbdab82c3a9cdd2809"
+checksum = "f73541b156d32197eecda1a4014d7f868fd2bcb3c550d5386087cfba442bf69c"
 dependencies = [
  "pest",
  "pest_generator",
@@ -2461,22 +2493,22 @@ dependencies = [
 
 [[package]]
 name = "pest_generator"
-version = "2.7.7"
+version = "2.7.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1381c29a877c6d34b8c176e734f35d7f7f5b3adaefe940cb4d1bb7af94678e2e"
+checksum = "c35eeed0a3fab112f75165fdc026b3913f4183133f19b49be773ac9ea966e8bd"
 dependencies = [
  "pest",
  "pest_meta",
  "proc-macro2",
  "quote",
- "syn 2.0.51",
+ "syn 2.0.57",
 ]
 
 [[package]]
 name = "pest_meta"
-version = "2.7.7"
+version = "2.7.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0934d6907f148c22a3acbda520c7eed243ad7487a30f51f6ce52b58b7077a8a"
+checksum = "2adbf29bb9776f28caece835398781ab24435585fe0d4dc1374a61db5accedca"
 dependencies = [
  "once_cell",
  "pest",
@@ -2490,34 +2522,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
 dependencies = [
  "fixedbitset",
- "indexmap 2.2.3",
+ "indexmap 2.2.6",
 ]
 
 [[package]]
 name = "pin-project"
-version = "1.1.4"
+version = "1.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0"
+checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3"
 dependencies = [
  "pin-project-internal",
 ]
 
 [[package]]
 name = "pin-project-internal"
-version = "1.1.4"
+version = "1.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690"
+checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.51",
+ "syn 2.0.57",
 ]
 
 [[package]]
 name = "pin-project-lite"
-version = "0.2.13"
+version = "0.2.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
+checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
 
 [[package]]
 name = "pin-utils"
@@ -2544,7 +2576,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4"
 dependencies = [
  "atomic-waker",
- "fastrand 2.0.1",
+ "fastrand 2.0.2",
  "futures-io",
 ]
 
@@ -2572,14 +2604,15 @@ dependencies = [
 
 [[package]]
 name = "polling"
-version = "3.5.0"
+version = "3.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24f040dee2588b4963afb4e420540439d126f73fdacf4a9c486a96d840bac3c9"
+checksum = "e0c976a60b2d7e99d6f229e414670a9b85d13ac305cc6d1e9c134de58c5aaaf6"
 dependencies = [
  "cfg-if",
  "concurrent-queue",
+ "hermit-abi",
  "pin-project-lite",
- "rustix 0.38.31",
+ "rustix 0.38.32",
  "tracing",
  "windows-sys 0.52.0",
 ]
@@ -2622,9 +2655,9 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.78"
+version = "1.0.79"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
+checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
 dependencies = [
  "unicode-ident",
 ]
@@ -2635,7 +2668,7 @@ version = "0.9.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b"
 dependencies = [
- "bitflags 2.4.2",
+ "bitflags 2.5.0",
  "memchr",
  "unicase",
 ]
@@ -2760,9 +2793,9 @@ dependencies = [
 
 [[package]]
 name = "redox_users"
-version = "0.4.4"
+version = "0.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4"
+checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891"
 dependencies = [
  "getrandom 0.2.12",
  "libredox",
@@ -2771,14 +2804,14 @@ dependencies = [
 
 [[package]]
 name = "regex"
-version = "1.10.3"
+version = "1.10.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
+checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
 dependencies = [
  "aho-corasick",
  "memchr",
- "regex-automata 0.4.5",
- "regex-syntax 0.8.2",
+ "regex-automata 0.4.6",
+ "regex-syntax 0.8.3",
 ]
 
 [[package]]
@@ -2792,13 +2825,13 @@ dependencies = [
 
 [[package]]
 name = "regex-automata"
-version = "0.4.5"
+version = "0.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
+checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
 dependencies = [
  "aho-corasick",
  "memchr",
- "regex-syntax 0.8.2",
+ "regex-syntax 0.8.3",
 ]
 
 [[package]]
@@ -2809,15 +2842,15 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
 
 [[package]]
 name = "regex-syntax"
-version = "0.8.2"
+version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
+checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
 
 [[package]]
 name = "reqwest"
-version = "0.11.24"
+version = "0.11.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251"
+checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
 dependencies = [
  "base64 0.21.7",
  "bytes",
@@ -2865,8 +2898,8 @@ version = "1.17.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f6273372244d04a8a4b0bec080ea1e710403e88c5d9d83f9808b2bfa64f0982a"
 dependencies = [
- "ahash 0.8.10",
- "bitflags 2.4.2",
+ "ahash 0.8.11",
+ "bitflags 2.5.0",
  "instant",
  "num-traits",
  "once_cell",
@@ -2885,7 +2918,7 @@ checksum = "9db7f8dc4c9d48183a17ce550574c42995252b82d267eaca3fcd1b979159856c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.51",
+ "syn 2.0.57",
 ]
 
 [[package]]
@@ -2980,11 +3013,11 @@ dependencies = [
 
 [[package]]
 name = "rustix"
-version = "0.38.31"
+version = "0.38.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
+checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89"
 dependencies = [
- "bitflags 2.4.2",
+ "bitflags 2.5.0",
  "errno",
  "libc",
  "linux-raw-sys 0.4.13",
@@ -3058,6 +3091,12 @@ dependencies = [
  "untrusted 0.9.0",
 ]
 
+[[package]]
+name = "rustversion"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
+
 [[package]]
 name = "ryu"
 version = "1.0.17"
@@ -3100,9 +3139,9 @@ dependencies = [
 
 [[package]]
 name = "security-framework"
-version = "2.9.2"
+version = "2.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de"
+checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6"
 dependencies = [
  "bitflags 1.3.2",
  "core-foundation",
@@ -3113,9 +3152,9 @@ dependencies = [
 
 [[package]]
 name = "security-framework-sys"
-version = "2.9.1"
+version = "2.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a"
+checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef"
 dependencies = [
  "core-foundation-sys",
  "libc",
@@ -3147,14 +3186,14 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.51",
+ "syn 2.0.57",
 ]
 
 [[package]]
 name = "serde_json"
-version = "1.0.114"
+version = "1.0.115"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
+checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd"
 dependencies = [
  "itoa",
  "ryu",
@@ -3163,9 +3202,9 @@ dependencies = [
 
 [[package]]
 name = "serde_path_to_error"
-version = "0.1.15"
+version = "0.1.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ebd154a240de39fdebcf5775d2675c204d7c13cf39a4c697be6493c8e734337c"
+checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6"
 dependencies = [
  "itoa",
  "serde",
@@ -3200,7 +3239,7 @@ version = "0.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "70c0e00fab6460447391a1981c21341746bc2d0178a7c46a3bbf667f450ac6e4"
 dependencies = [
- "indexmap 2.2.3",
+ "indexmap 2.2.6",
  "itertools",
  "num-traits",
  "once_cell",
@@ -3225,7 +3264,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "strsim 0.11.0",
- "syn 2.0.51",
+ "syn 2.0.57",
 ]
 
 [[package]]
@@ -3240,11 +3279,11 @@ dependencies = [
 
 [[package]]
 name = "serde_yaml"
-version = "0.9.32"
+version = "0.9.34+deprecated"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fd075d994154d4a774f95b51fb96bdc2832b0ea48425c92546073816cda1f2f"
+checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
 dependencies = [
- "indexmap 2.2.3",
+ "indexmap 2.2.6",
  "itoa",
  "ryu",
  "serde",
@@ -3315,11 +3354,42 @@ dependencies = [
  "autocfg",
 ]
 
+[[package]]
+name = "slog"
+version = "2.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06"
+
+[[package]]
+name = "slog-async"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72c8038f898a2c79507940990f05386455b3a317d8f18d4caea7cbc3d5096b84"
+dependencies = [
+ "crossbeam-channel",
+ "slog",
+ "take_mut",
+ "thread_local",
+]
+
+[[package]]
+name = "slog-term"
+version = "2.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6e022d0b998abfe5c3782c1f03551a596269450ccd677ea51c56f8b214610e8"
+dependencies = [
+ "is-terminal",
+ "slog",
+ "term",
+ "thread_local",
+ "time 0.3.34",
+]
+
 [[package]]
 name = "smallvec"
-version = "1.13.1"
+version = "1.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
+checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
 dependencies = [
  "serde",
 ]
@@ -3394,12 +3464,12 @@ dependencies = [
 
 [[package]]
 name = "sqlx"
-version = "0.7.3"
+version = "0.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dba03c279da73694ef99763320dea58b51095dfe87d001b1d4b5fe78ba8763cf"
+checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa"
 dependencies = [
- "sqlx-core 0.7.3",
- "sqlx-macros 0.7.3",
+ "sqlx-core 0.7.4",
+ "sqlx-macros 0.7.4",
  "sqlx-postgres",
 ]
 
@@ -3412,7 +3482,7 @@ dependencies = [
  "async-trait",
  "casbin",
  "dotenv",
- "sqlx 0.7.3",
+ "sqlx 0.7.4",
 ]
 
 [[package]]
@@ -3472,17 +3542,16 @@ dependencies = [
 
 [[package]]
 name = "sqlx-core"
-version = "0.7.3"
+version = "0.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d84b0a3c3739e220d94b3239fd69fb1f74bc36e16643423bd99de3b43c21bfbd"
+checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6"
 dependencies = [
- "ahash 0.8.10",
+ "ahash 0.8.11",
  "atoi 2.0.0",
  "byteorder",
  "bytes",
  "crc",
  "crossbeam-queue",
- "dotenvy",
  "either",
  "event-listener 2.5.3",
  "futures-channel",
@@ -3492,7 +3561,7 @@ dependencies = [
  "futures-util",
  "hashlink",
  "hex",
- "indexmap 2.2.3",
+ "indexmap 2.2.6",
  "log",
  "memchr",
  "native-tls",
@@ -3519,7 +3588,7 @@ checksum = "9966e64ae989e7e575b19d7265cb79d7fc3cbbdf179835cb0d716f294c2049c9"
 dependencies = [
  "dotenvy",
  "either",
- "heck",
+ "heck 0.4.1",
  "hex",
  "once_cell",
  "proc-macro2",
@@ -3535,27 +3604,26 @@ dependencies = [
 
 [[package]]
 name = "sqlx-macros"
-version = "0.7.3"
+version = "0.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89961c00dc4d7dffb7aee214964b065072bff69e36ddb9e2c107541f75e4f2a5"
+checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127"
 dependencies = [
  "proc-macro2",
  "quote",
- "sqlx-core 0.7.3",
+ "sqlx-core 0.7.4",
  "sqlx-macros-core",
  "syn 1.0.109",
 ]
 
 [[package]]
 name = "sqlx-macros-core"
-version = "0.7.3"
+version = "0.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0bd4519486723648186a08785143599760f7cc81c52334a55d6a83ea1e20841"
+checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8"
 dependencies = [
- "atomic-write-file",
  "dotenvy",
  "either",
- "heck",
+ "heck 0.4.1",
  "hex",
  "once_cell",
  "proc-macro2",
@@ -3563,7 +3631,7 @@ dependencies = [
  "serde",
  "serde_json",
  "sha2",
- "sqlx-core 0.7.3",
+ "sqlx-core 0.7.4",
  "sqlx-postgres",
  "syn 1.0.109",
  "tempfile",
@@ -3573,13 +3641,13 @@ dependencies = [
 
 [[package]]
 name = "sqlx-postgres"
-version = "0.7.3"
+version = "0.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d6ac0ac3b7ccd10cc96c7ab29791a7dd236bd94021f31eec7ba3d46a74aa1c24"
+checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e"
 dependencies = [
  "atoi 2.0.0",
  "base64 0.21.7",
- "bitflags 2.4.2",
+ "bitflags 2.5.0",
  "byteorder",
  "crc",
  "dotenvy",
@@ -3600,10 +3668,9 @@ dependencies = [
  "rand 0.8.5",
  "serde",
  "serde_json",
- "sha1",
  "sha2",
  "smallvec",
- "sqlx-core 0.7.3",
+ "sqlx-core 0.7.4",
  "stringprep",
  "thiserror",
  "tracing",
@@ -3638,11 +3705,11 @@ dependencies = [
  "derive_builder 0.12.0",
  "docker-compose-types",
  "futures",
- "futures-lite 2.2.0",
+ "futures-lite 2.3.0",
  "futures-util",
  "glob",
  "hmac",
- "indexmap 2.2.3",
+ "indexmap 2.2.6",
  "lapin",
  "rand 0.8.5",
  "regex",
@@ -3716,9 +3783,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.51"
+version = "2.0.57"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ab617d94515e94ae53b8406c628598680aa0c9587474ecbe58188f7b345d66c"
+checksum = "11a6ae1e52eb25aab8f3fb9fca13be982a373b8f1157ca14b897a825ba4a2d35"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -3758,6 +3825,12 @@ version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417"
 
+[[package]]
+name = "take_mut"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60"
+
 [[package]]
 name = "tcp-stream"
 version = "0.26.1"
@@ -3777,11 +3850,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
 dependencies = [
  "cfg-if",
- "fastrand 2.0.1",
- "rustix 0.38.31",
+ "fastrand 2.0.2",
+ "rustix 0.38.32",
  "windows-sys 0.52.0",
 ]
 
+[[package]]
+name = "term"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f"
+dependencies = [
+ "dirs-next",
+ "rustversion",
+ "winapi",
+]
+
 [[package]]
 name = "thin-vec"
 version = "0.2.13"
@@ -3793,22 +3877,22 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "1.0.57"
+version = "1.0.58"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
+checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.57"
+version = "1.0.58"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
+checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.51",
+ "syn 2.0.57",
 ]
 
 [[package]]
@@ -3889,9 +3973,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
 
 [[package]]
 name = "tokio"
-version = "1.36.0"
+version = "1.37.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931"
+checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787"
 dependencies = [
  "backtrace",
  "bytes",
@@ -3925,7 +4009,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.51",
+ "syn 2.0.57",
 ]
 
 [[package]]
@@ -3951,9 +4035,9 @@ dependencies = [
 
 [[package]]
 name = "tokio-stream"
-version = "0.1.14"
+version = "0.1.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842"
+checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af"
 dependencies = [
  "futures-core",
  "pin-project-lite",
@@ -4003,9 +4087,9 @@ dependencies = [
 
 [[package]]
 name = "tracing-actix-web"
-version = "0.7.9"
+version = "0.7.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fe0d5feac3f4ca21ba33496bcb1ccab58cca6412b1405ae80f0581541e0ca78"
+checksum = "fa069bd1503dd526ee793bb3fce408895136c95fc86d2edb2acf1c646d7f0684"
 dependencies = [
  "actix-web",
  "mutually_exclusive_features",
@@ -4022,7 +4106,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.51",
+ "syn 2.0.57",
 ]
 
 [[package]]
@@ -4031,7 +4115,7 @@ version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b5c266b9ac83dedf0e0385ad78514949e6d89491269e7065bee51d2bb8ec7373"
 dependencies = [
- "ahash 0.8.10",
+ "ahash 0.8.11",
  "gethostname",
  "log",
  "serde",
@@ -4161,9 +4245,9 @@ checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
 
 [[package]]
 name = "unsafe-libyaml"
-version = "0.2.10"
+version = "0.2.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b"
+checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
 
 [[package]]
 name = "untrusted"
@@ -4197,9 +4281,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
 
 [[package]]
 name = "uuid"
-version = "1.7.0"
+version = "1.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a"
+checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
 dependencies = [
  "getrandom 0.2.12",
  "serde",
@@ -4231,9 +4315,9 @@ checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690"
 
 [[package]]
 name = "walkdir"
-version = "2.4.0"
+version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
 dependencies = [
  "same-file",
  "winapi-util",
@@ -4266,11 +4350,17 @@ version = "0.11.0+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
+[[package]]
+name = "wasite"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
+
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.91"
+version = "0.2.92"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f"
+checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
 dependencies = [
  "cfg-if",
  "wasm-bindgen-macro",
@@ -4278,24 +4368,24 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.91"
+version = "0.2.92"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b"
+checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
 dependencies = [
  "bumpalo",
  "log",
  "once_cell",
  "proc-macro2",
  "quote",
- "syn 2.0.51",
+ "syn 2.0.57",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-futures"
-version = "0.4.41"
+version = "0.4.42"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97"
+checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0"
 dependencies = [
  "cfg-if",
  "js-sys",
@@ -4305,9 +4395,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.91"
+version = "0.2.92"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed"
+checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -4315,28 +4405,28 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.91"
+version = "0.2.92"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66"
+checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.51",
+ "syn 2.0.57",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.91"
+version = "0.2.92"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838"
+checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
 
 [[package]]
 name = "web-sys"
-version = "0.3.68"
+version = "0.3.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446"
+checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
@@ -4363,11 +4453,12 @@ dependencies = [
 
 [[package]]
 name = "whoami"
-version = "1.4.1"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50"
+checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9"
 dependencies = [
- "wasm-bindgen",
+ "redox_syscall 0.4.1",
+ "wasite",
  "web-sys",
 ]
 
@@ -4408,7 +4499,7 @@ version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
 dependencies = [
- "windows-targets 0.52.3",
+ "windows-targets 0.52.4",
 ]
 
 [[package]]
@@ -4426,7 +4517,7 @@ version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
 dependencies = [
- "windows-targets 0.52.3",
+ "windows-targets 0.52.4",
 ]
 
 [[package]]
@@ -4446,17 +4537,17 @@ dependencies = [
 
 [[package]]
 name = "windows-targets"
-version = "0.52.3"
+version = "0.52.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f"
+checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
 dependencies = [
- "windows_aarch64_gnullvm 0.52.3",
- "windows_aarch64_msvc 0.52.3",
- "windows_i686_gnu 0.52.3",
- "windows_i686_msvc 0.52.3",
- "windows_x86_64_gnu 0.52.3",
- "windows_x86_64_gnullvm 0.52.3",
- "windows_x86_64_msvc 0.52.3",
+ "windows_aarch64_gnullvm 0.52.4",
+ "windows_aarch64_msvc 0.52.4",
+ "windows_i686_gnu 0.52.4",
+ "windows_i686_msvc 0.52.4",
+ "windows_x86_64_gnu 0.52.4",
+ "windows_x86_64_gnullvm 0.52.4",
+ "windows_x86_64_msvc 0.52.4",
 ]
 
 [[package]]
@@ -4467,9 +4558,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
 
 [[package]]
 name = "windows_aarch64_gnullvm"
-version = "0.52.3"
+version = "0.52.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6"
+checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
 
 [[package]]
 name = "windows_aarch64_msvc"
@@ -4479,9 +4570,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
 
 [[package]]
 name = "windows_aarch64_msvc"
-version = "0.52.3"
+version = "0.52.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f"
+checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
 
 [[package]]
 name = "windows_i686_gnu"
@@ -4491,9 +4582,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
 
 [[package]]
 name = "windows_i686_gnu"
-version = "0.52.3"
+version = "0.52.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb"
+checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
 
 [[package]]
 name = "windows_i686_msvc"
@@ -4503,9 +4594,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
 
 [[package]]
 name = "windows_i686_msvc"
-version = "0.52.3"
+version = "0.52.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58"
+checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
 
 [[package]]
 name = "windows_x86_64_gnu"
@@ -4515,9 +4606,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
 
 [[package]]
 name = "windows_x86_64_gnu"
-version = "0.52.3"
+version = "0.52.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614"
+checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
 
 [[package]]
 name = "windows_x86_64_gnullvm"
@@ -4527,9 +4618,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
 
 [[package]]
 name = "windows_x86_64_gnullvm"
-version = "0.52.3"
+version = "0.52.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c"
+checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
 
 [[package]]
 name = "windows_x86_64_msvc"
@@ -4539,9 +4630,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
 
 [[package]]
 name = "windows_x86_64_msvc"
-version = "0.52.3"
+version = "0.52.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6"
+checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
 
 [[package]]
 name = "winreg"
@@ -4607,32 +4698,32 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.51",
+ "syn 2.0.57",
 ]
 
 [[package]]
 name = "zstd"
-version = "0.13.0"
+version = "0.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bffb3309596d527cfcba7dfc6ed6052f1d39dfbd7c867aa2e865e4a449c10110"
+checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a"
 dependencies = [
  "zstd-safe",
 ]
 
 [[package]]
 name = "zstd-safe"
-version = "7.0.0"
+version = "7.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43747c7422e2924c11144d5229878b98180ef8b06cca4ab5af37afc8a8d8ea3e"
+checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a"
 dependencies = [
  "zstd-sys",
 ]
 
 [[package]]
 name = "zstd-sys"
-version = "2.0.9+zstd.1.5.5"
+version = "2.0.10+zstd.1.5.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656"
+checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa"
 dependencies = [
  "cc",
  "pkg-config",
diff --git a/Cargo.toml b/Cargo.toml
index d005e9be..0f3e51f8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -75,6 +75,7 @@ features = [
 [features]
 default = ["indexmap"]
 indexmap = ["dep:indexmap"]
+explain = ["actix-casbin-auth/explain", "actix-casbin-auth/logging"]
 
 [dev-dependencies]
 glob = "0.3"
diff --git a/src/console/commands/debug/casbin.rs b/src/console/commands/debug/casbin.rs
index 25137270..b05fb2e2 100644
--- a/src/console/commands/debug/casbin.rs
+++ b/src/console/commands/debug/casbin.rs
@@ -39,7 +39,7 @@ impl crate::console::commands::CallableTrait for CasbinCommand {
 
             println!("{}", policies.len());
 
-            match lock.enforce_mut(vec!["".to_string(), self.subject.clone(), self.path.clone(), self.action.clone()]) {
+            match lock.enforce_mut(vec![self.subject.clone(), self.path.clone(), self.action.clone()]) {
                 Ok(true) => println!("TRUE"),
                 Ok(false) => println!("FALSE"),
                 Err(err) => {

From 57c76712357f4a9c0806705774729458b29a849f Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Wed, 3 Apr 2024 22:08:05 +0300
Subject: [PATCH 253/284] casbin-debug

---
 Cargo.lock                           | 52 ++++++++++++++--------------
 README.md                            |  2 +-
 src/console/commands/debug/casbin.rs |  8 ++---
 3 files changed, 31 insertions(+), 31 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index d93e7a9b..67e6d460 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5,7 +5,7 @@ version = 3
 [[package]]
 name = "actix-casbin-auth"
 version = "1.0.0"
-source = "git+https://github.com/smart--petea/actix-casbin-auth.git?branch=dirty-master#2e0c3ae76fa27a3bd515478317e487082b8e8294"
+source = "git+https://github.com/smart--petea/actix-casbin-auth.git?branch=dirty-master#bbc6d08900521c684c46bab59c4b92394ca5bd57"
 dependencies = [
  "actix-service",
  "actix-web",
@@ -92,7 +92,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb"
 dependencies = [
  "quote",
- "syn 2.0.57",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -205,7 +205,7 @@ dependencies = [
  "actix-router",
  "proc-macro2",
  "quote",
- "syn 2.0.57",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -554,7 +554,7 @@ checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.57",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -839,7 +839,7 @@ dependencies = [
  "anstream",
  "anstyle",
  "clap_lex",
- "strsim 0.11.0",
+ "strsim 0.11.1",
 ]
 
 [[package]]
@@ -851,7 +851,7 @@ dependencies = [
  "heck 0.5.0",
  "proc-macro2",
  "quote",
- "syn 2.0.57",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -1608,7 +1608,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.57",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -1705,9 +1705,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
 
 [[package]]
 name = "h2"
-version = "0.3.25"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fbd2820c5e49886948654ab546d0688ff24530286bdcf8fca3cefb16d4618eb"
+checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8"
 dependencies = [
  "bytes",
  "fnv",
@@ -2344,7 +2344,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.57",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -2501,7 +2501,7 @@ dependencies = [
  "pest_meta",
  "proc-macro2",
  "quote",
- "syn 2.0.57",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -2542,7 +2542,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.57",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -2918,7 +2918,7 @@ checksum = "9db7f8dc4c9d48183a17ce550574c42995252b82d267eaca3fcd1b979159856c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.57",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -3186,7 +3186,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.57",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -3263,8 +3263,8 @@ dependencies = [
  "proc-macro-error",
  "proc-macro2",
  "quote",
- "strsim 0.11.0",
- "syn 2.0.57",
+ "strsim 0.11.1",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -3760,9 +3760,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
 
 [[package]]
 name = "strsim"
-version = "0.11.0"
+version = "0.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
 
 [[package]]
 name = "subtle"
@@ -3783,9 +3783,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.57"
+version = "2.0.58"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11a6ae1e52eb25aab8f3fb9fca13be982a373b8f1157ca14b897a825ba4a2d35"
+checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -3892,7 +3892,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.57",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -4009,7 +4009,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.57",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -4106,7 +4106,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.57",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -4377,7 +4377,7 @@ dependencies = [
  "once_cell",
  "proc-macro2",
  "quote",
- "syn 2.0.57",
+ "syn 2.0.58",
  "wasm-bindgen-shared",
 ]
 
@@ -4411,7 +4411,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.57",
+ "syn 2.0.58",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
@@ -4698,7 +4698,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.57",
+ "syn 2.0.58",
 ]
 
 [[package]]
diff --git a/README.md b/README.md
index ad44ea73..0541a600 100644
--- a/README.md
+++ b/README.md
@@ -88,4 +88,4 @@ http://localhost:8000/test/deploy
 
 
 Test casbin rule
-cargo r --bin console debug casbin --path "/project/:id" --action "POST" --subject "hy181TZa4DaabUZWklsrxw"
\ No newline at end of file
+cargo r --bin console --features=explain debug casbin --path /client --action POST --subject admin_petru
diff --git a/src/console/commands/debug/casbin.rs b/src/console/commands/debug/casbin.rs
index b05fb2e2..8a14695f 100644
--- a/src/console/commands/debug/casbin.rs
+++ b/src/console/commands/debug/casbin.rs
@@ -31,14 +31,14 @@ impl crate::console::commands::CallableTrait for CasbinCommand {
 
             let mut authorizationService = middleware::authorization::try_new(settings.database.connection_string()).await?;
             let casbin_enforcer = authorizationService.get_enforcer();
+
             let mut lock = casbin_enforcer.write().await;
             let policies = lock.get_model().get_model().get("p").unwrap().get("p").unwrap().get_policy();
-            for policy in policies {
-                println!("{policy:?}");
+            for (pos, policy) in policies.iter().enumerate() {
+                println!("{pos}: {policy:?}");
             }
 
-            println!("{}", policies.len());
-
+            lock.enable_log(true);
             match lock.enforce_mut(vec![self.subject.clone(), self.path.clone(), self.action.clone()]) {
                 Ok(true) => println!("TRUE"),
                 Ok(false) => println!("FALSE"),

From d52877e6b93f0048e2e98ce89daaf9d2d0adf3b8 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Thu, 4 Apr 2024 10:09:09 +0300
Subject: [PATCH 254/284] casbin-debug

---
 Cargo.toml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Cargo.toml b/Cargo.toml
index 0f3e51f8..87b9ce74 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,6 +14,7 @@ name = "server"
 [[bin]]
 path = "src/console/main.rs"
 name = "console"
+required-features = ["explain"]
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 

From 41b1ca6e182d19a0eec0b460d45db38632bdbee4 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Sun, 7 Apr 2024 09:04:45 +0300
Subject: [PATCH 255/284] unfinished tag fix

---
 src/helpers/dockerhub.rs                  | 195 ++++++++++++++++++----
 tests/mock_data/custom-stack-payload.json |   2 +-
 2 files changed, 161 insertions(+), 36 deletions(-)

diff --git a/src/helpers/dockerhub.rs b/src/helpers/dockerhub.rs
index 81f4feb2..60ace373 100644
--- a/src/helpers/dockerhub.rs
+++ b/src/helpers/dockerhub.rs
@@ -65,29 +65,58 @@ pub struct RepoResults {
     pub results: Vec<RepoResult>,
 }
 
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
+pub struct OfficialRepoResults {
+    pub count: Option<i64>,
+    pub next: Option<Value>,
+    pub previous: Option<Value>,
+    pub results: Vec<OfficialRepoResult>,
+}
+
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct RepoResult {
     pub name: String,
-    pub namespace: String,
-    pub repository_type: String,
-    pub status: i64,
-    pub status_description: String,
-    pub description: String,
-    pub is_private: bool,
-    pub star_count: i64,
-    pub pull_count: i64,
+    pub namespace: Option<String>,
+    pub repository_type: Option<String>,
+    pub status: Option<i64>,
+    pub status_description: Option<String>,
+    pub description: Option<String>,
+    pub is_private: Option<bool>,
+    pub star_count: Option<i64>,
+    pub pull_count: Option<i64>,
     pub last_updated: String,
-    pub date_registered: String,
-    pub affiliation: String,
-    pub media_types: Vec<String>,
-    pub content_types: Vec<String>,
+    pub date_registered: Option<String>,
+    pub affiliation: Option<String>,
+    pub media_types: Option<Vec<String>>,
+    pub content_types: Option<Vec<String>>,
 }
 
+
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct OfficialRepoResult {
+    pub images: Vec<Image>,
+    pub last_updated: String,
+    pub last_updater: i64,
+    pub content_type: String,
+    pub creator: i64,
+    pub digest: Option<String>,
+    pub full_size: i64,
+    pub id: i64,
+    pub last_updater_username: String,
+    pub media_type: String,
+    pub name: String,
+    pub repository: i64,
+    pub tag_last_pulled: Option<String>,
+    pub tag_last_pushed: Option<String>,
+    pub tag_status: String,
+    pub v2: bool,
+}
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Validate)]
 pub struct DockerHub<'a> {
     pub(crate) creds: DockerHubCreds<'a>,
     pub(crate) repos: String,
     pub(crate) image: String,
+    pub(crate) tag: Option<String>,
 }
 
 impl<'a> DockerHub<'a> {
@@ -105,33 +134,86 @@ impl<'a> DockerHub<'a> {
             .map_err(|err| format!("{:?}", err))
     }
 
-    pub async fn lookup_public_repo(&self) -> Result<bool, String> {
-        let url = format!("https://hub.docker.com/v2/repositories/{}", &self.repos);
-        tracing::debug!("Validate public repositories {:?}", url);
+    pub async fn lookup_public_repos(&'a self) -> Result<bool, String> {
+
+        let url = format!("https://hub.docker.com/v2/repositories/{}", self.repos);
+        tracing::debug!("Validate public repository {:?}", &url);
         let client = reqwest::Client::new()
-            .get(url)
+            .get(&url)
             .header("Accept", "application/json");
         let client = self.set_token(client).await?;
         client
             .send()
             .await
             .map_err(|err| {
-                tracing::debug!("🟥Error response {:?}", err);
-                format!("{}", err)
+                let msg = format!("🟥Error response {:?}", err);
+                tracing::debug!(msg);
+                msg
             })?
             .json::<RepoResults>()
             .await
-            .map_err(|err| format!("🟥Error on getting results:: {}", err))
+            .map_err(|err| {
+                let msg = format!("🟥Error on getting results:: {} url: {}", &err, &url);
+                tracing::error!(msg);
+                msg
+            })
             .map(|repositories| {
-                tracing::debug!("Get public image repositories response {:?}", repositories);
+                tracing::debug!("Get public image repo {:?} response {:?}", &url, repositories);
                 if repositories.count.unwrap_or(0) > 0 {
                     // let's find at least one active tag
                     let active = repositories
                         .results
                         .into_iter()
-                        .any(|repo| repo.status == 1);
-                    tracing::debug!("✅ Image is active");
+                        .any(|repo| repo.status == Some(1));
+                    tracing::debug!("✅ Image is active. url: {:?}", &url);
                     active
+                } else {
+                    tracing::debug!("🟥 Image tag is not active, url: {:?}", &url);
+                    false
+                }
+            })
+    }
+
+    pub async fn lookup_official_repos(&'a self) -> Result<bool, String> {
+        // search in official library repositories
+        let url = format!("https://hub.docker.com/v2/repositories/library/{}/tags", self.repos);
+        return self.lookup(&url).await;
+    }
+
+    pub async fn lookup(&'a self, url: &String) -> Result<bool, String> {
+        tracing::debug!("Search official repos {:?}", url);
+        let client = reqwest::Client::new()
+            .get(url)
+            .header("Accept", "application/json");
+        let client = self.set_token(client).await?;
+        client
+            .send()
+            .await
+            .map_err(|err| format!("🟥{}", err))?
+            .json::<OfficialRepoResults>()
+            .await
+            .map_err(|err| {
+                tracing::debug!("🟥Error response {:?}", err);
+                format!("{}", err)
+            })
+            .map(|tags| {
+                tracing::debug!("Validate official image response {:?}", tags);
+                if tags.count.unwrap_or(0) > 0 {
+                    // let's find at least one active tag
+                    let result = tags
+                        .results
+                        .into_iter()
+                        .any(|tag| {
+                            tracing::debug!("official: {:?}", tag);
+                            if "active".to_string() == tag.tag_status {
+                                tracing::debug!("✅ Image is active");
+                                true
+                            } else {
+                                false
+                            }
+                        });
+                    tracing::debug!("✅ result is {:?}", result);
+                    result
                 } else {
                     tracing::debug!("🟥 Image tag is not active");
                     false
@@ -139,7 +221,7 @@ impl<'a> DockerHub<'a> {
             })
     }
 
-    pub async fn lookup_private_repo(&self) -> Result<bool, String> {
+    pub async fn lookup_private_repo(&'a self) -> Result<bool, String> {
         let url = format!(
             "https://hub.docker.com/v2/namespaces/{}/repositories/{}/tags",
             &self.creds.username, &self.repos
@@ -168,29 +250,60 @@ impl<'a> DockerHub<'a> {
                         .into_iter()
                         .any(|tag| tag.tag_status.contains("active"));
                     tracing::debug!("✅ Image is active");
-                    active
+                    return active;
                 } else {
                     tracing::debug!("🟥 Image tag is not active");
                     false
                 }
             })
     }
-    pub async fn is_active(&'a self) -> Result<bool, String> {
+    pub async fn is_active(&'a mut self) -> Result<bool, String> {
         // if namespace/user is not set change endpoint and return a different response
+
+        // let repo = self.repos.clone();
+        // if self.repos.contains(':') {
+        //         let _repo = repo.split(':')
+        //         .collect::<Vec<&str>>();
+        //     self.repos = _repo.first().expect("split error").to_string();
+        //     self.tag = Some(_repo.last().expect("split error").to_string());
+        // }
+
+        // let n = self.repos.split(':').collect::<Vec<&str>>();
+        // if let Some(name: &str) == n.first() {
+        //     self.repos = name.to_string();
+        // }
+        // if let Some(name: &str) == n.last() {
+        //     self.tag = name.to_string();
+        // }
+
         if self.creds.username.is_empty() {
-            match self.lookup_public_repo().await {
-                Ok(result) => Ok(result),
-                Err(_e) => Ok(false),
-            }
+
+            if Ok(true) == self.lookup_official_repos().await {
+                tracing::debug!("official: true");
+                return Ok(true);
+            } else {
+                tracing::debug!("official: false");
+            };
+
+            if Ok(true) == self.lookup_public_repos().await {
+                tracing::debug!("public: true");
+                return Ok(true);
+            };
+
+            Ok(false)
+
         } else {
-            match self.lookup_private_repo().await {
-                Ok(result) => Ok(result),
-                Err(_e) => Ok(false),
-            }
+
+            if Ok(true) == self.lookup_private_repo().await {
+                tracing::debug!("private: true");
+                return Ok(true);
+            };
+
+            Ok(false)
         }
     }
 
-    pub async fn set_token(&self, client: RequestBuilder) -> Result<RequestBuilder, String> {
+    pub async fn set_token(&'a self, client: RequestBuilder) -> Result<RequestBuilder, String> {
         if self.creds.password.is_empty() {
             tracing::debug!("Password is empty. Image should be public");
             return Ok(client);
@@ -217,13 +330,25 @@ impl<'a> From<&'a DockerImage> for DockerHub<'a> {
             None => "",
         };
 
+        let name = image.dockerhub_name.clone().unwrap_or("".to_string());
+        let mut tag = "".to_string();
+
+        if name.contains(':') {
+            let parts = name
+                .split(':')
+                .collect::<Vec<&str>>();
+            let name = parts.first().expect("Could not split").to_string();
+            tag = parts.last().expect("Could not split").to_string();
+        }
+
         DockerHub {
             creds: DockerHubCreds {
                 username: username,
                 password: password,
             },
-            repos: image.dockerhub_name.clone().unwrap_or("".to_string()),
+            repos: name,
             image: format!("{}", image),
+            tag: Some(tag),
         }
     }
 }
diff --git a/tests/mock_data/custom-stack-payload.json b/tests/mock_data/custom-stack-payload.json
index ab48715a..88fe2c25 100644
--- a/tests/mock_data/custom-stack-payload.json
+++ b/tests/mock_data/custom-stack-payload.json
@@ -1 +1 @@
-{"commonDomain":"","domainList":{},"region":"","zone":null,"server":"cx11","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":true,"disk_type":"pd-standart","cloud_token":"*******************","cloud_key":"*********","cloud_secret":"****************","disk_size":40,"provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-annually","custom":{"networks":[{"name":"default_network","id":"lsnd2ttg3ivhw3db5"}],"web":[{"_etag":null,"_id":"lsq2tdff3c0acf2lj","_created":"2023-04-28T09:46:19.470502","_updated":"2024-02-09T13:44:36.854036","name":"PostgREST","code":"postgrest","role":["postgrest"],"type":"web","default":false,"popularity":null,"descr":null,"ports":{"public":["3000","8080"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":null,"category_id":null,"parent_app_id":null,"full_description":null,"description":null,"plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.0","ram_size":null,"disk_size":null,"dockerhub_image":"postgrest","form":null,"requirements":null,"docker_image_is_internal":true,"custom_preset":{"volumes":[],"environment":[{"key":"PGRST_OPENAPI_SERVER_PROXY_URI","value":"http://DOMAIN_NAME:3000"},{"key":"PGRST_DB_ANON_ROLE","value":"anon"},{"key":"PGRST_DB_SCHEMA","value":"public"},{"key":"PGHOST","value":"postgresql_host"},{"key":"PGPORT","value":"5432"},{"key":"PGUSER","value":"user"},{"key":"PGDATABASE","value":"database"},{"key":"PGPASSWORD ","value":"password"}],"shared_ports":[{"host_port":"3000","container_port":"3000"}],"restart":"always"},"category":[null],"group":[],"versions":[{"_id":556,"name":"Postgrest","version":"latest","update_status":"published","tag":"latest"}],"links":[{"url":"https://postgrest.org","title":"Vendor","type":"vendor","follow":false},{"repo_owner":"PostgREST","repo_name":"postgrest","type":"github","follow":false}],"version":{"_id":556,"name":"Postgrest","version":"latest","update_status":"published","tag":"latest"},"network":["lsnd2ttg3ivhw3db5"],"restart":"always","timestamp":"2024-02-17T12:50:01.659Z","volumes":[],"environment":[{"key":"PGRST_OPENAPI_SERVER_PROXY_URI","value":"http://DOMAIN_NAME:3000"},{"key":"PGRST_DB_ANON_ROLE","value":"anon"},{"key":"PGRST_DB_SCHEMA","value":"public"},{"key":"PGHOST","value":"postgresql_host"},{"key":"PGPORT","value":"5432"},{"key":"PGUSER","value":"user"},{"key":"PGDATABASE","value":"database"},{"key":"PGPASSWORD ","value":"password"}],"shared_ports":[{"host_port":"3000","container_port":"3000"}],"domain":"","dockerhub_name":"postgrest"}],"feature":[],"service":[],"servers_count":3,"project_name":"Databases","custom_stack_code":"databases"}}
\ No newline at end of file
+{"commonDomain":"","domainList":{},"region":"","zone":null,"server":"cx11","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":true,"disk_type":"pd-standart","cloud_token":"*******************","cloud_key":"*********","cloud_secret":"****************","disk_size":40,"provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-annually","custom":{"networks":[{"name":"default_network","id":"lsnd2ttg3ivhw3db5"}],"web":[{"_etag":null,"_id":"lsq2tdff3c0acf2lj","_created":"2023-04-28T09:46:19.470502","_updated":"2024-02-09T13:44:36.854036","name":"PostgREST","code":"postgrest","role":["postgrest"],"type":"web","default":false,"popularity":null,"descr":null,"ports":{"public":["3000","8080"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":null,"category_id":null,"parent_app_id":null,"full_description":null,"description":null,"plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.0","ram_size":null,"disk_size":null,"dockerhub_image":"","form":null,"requirements":null,"docker_image_is_internal":true,"custom_preset":{"volumes":[],"environment":[{"key":"PGRST_OPENAPI_SERVER_PROXY_URI","value":"http://DOMAIN_NAME:3000"},{"key":"PGRST_DB_ANON_ROLE","value":"anon"},{"key":"PGRST_DB_SCHEMA","value":"public"},{"key":"PGHOST","value":"postgresql_host"},{"key":"PGPORT","value":"5432"},{"key":"PGUSER","value":"user"},{"key":"PGDATABASE","value":"database"},{"key":"PGPASSWORD ","value":"password"}],"shared_ports":[{"host_port":"3000","container_port":"3000"}],"restart":"always"},"category":[null],"group":[],"versions":[{"_id":556,"name":"Postgrest","version":"latest","update_status":"published","tag":"latest"}],"links":[{"url":"https://postgrest.org","title":"Vendor","type":"vendor","follow":false},{"repo_owner":"PostgREST","repo_name":"postgrest","type":"github","follow":false}],"version":{"_id":556,"name":"Postgrest","version":"latest","update_status":"published","tag":"latest"},"network":["lsnd2ttg3ivhw3db5"],"restart":"always","timestamp":"2024-02-17T12:50:01.659Z","volumes":[],"environment":[{"key":"PGRST_OPENAPI_SERVER_PROXY_URI","value":"http://DOMAIN_NAME:3000"},{"key":"PGRST_DB_ANON_ROLE","value":"anon"},{"key":"PGRST_DB_SCHEMA","value":"public"},{"key":"PGHOST","value":"postgresql_host"},{"key":"PGPORT","value":"5432"},{"key":"PGUSER","value":"user"},{"key":"PGDATABASE","value":"database"},{"key":"PGPASSWORD ","value":"password"}],"shared_ports":[{"host_port":"3000","container_port":"3000"}],"domain":"","dockerhub_name":"mariadb:11.3.2"}],"feature":[],"service":[],"servers_count":3,"project_name":"Databases","custom_stack_code":"databases"}}
\ No newline at end of file

From 4bae859b938f518c0686c6d1e6c112764969f3c0 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sun, 7 Apr 2024 21:46:56 +0300
Subject: [PATCH 256/284] casbin-debug command

---
 README.md                            |  2 ++
 src/console/commands/debug/casbin.rs | 12 ++++--------
 2 files changed, 6 insertions(+), 8 deletions(-)

diff --git a/README.md b/README.md
index 0541a600..18723b4a 100644
--- a/README.md
+++ b/README.md
@@ -88,4 +88,6 @@ http://localhost:8000/test/deploy
 
 
 Test casbin rule
+```
 cargo r --bin console --features=explain debug casbin --path /client --action POST --subject admin_petru
+```
diff --git a/src/console/commands/debug/casbin.rs b/src/console/commands/debug/casbin.rs
index 8a14695f..ba6bb623 100644
--- a/src/console/commands/debug/casbin.rs
+++ b/src/console/commands/debug/casbin.rs
@@ -38,15 +38,11 @@ impl crate::console::commands::CallableTrait for CasbinCommand {
                 println!("{pos}: {policy:?}");
             }
 
-            lock.enable_log(true);
-            match lock.enforce_mut(vec![self.subject.clone(), self.path.clone(), self.action.clone()]) {
-                Ok(true) => println!("TRUE"),
-                Ok(false) => println!("FALSE"),
-                Err(err) => {
-                    println!("err {err:?}");
-                }
+            #[cfg(feature = "explain")]
+            {
+                lock.enable_log(true);
             }
-            drop(lock);
+            lock.enforce_mut(vec![self.subject.clone(), self.path.clone(), self.action.clone()]); 
 
             Ok(())
         })

From b943f4cf005414b29bc1d531ba5aae9ff4839f74 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Mon, 8 Apr 2024 10:40:02 +0300
Subject: [PATCH 257/284] from to try_from, repo name format error fix

---
 src/forms/project/docker_image.rs |  2 +-
 src/helpers/dockerhub.rs          | 84 ++++++++++++++++++++-----------
 src/routes/project/update.rs      | 23 +++++++--
 3 files changed, 76 insertions(+), 33 deletions(-)

diff --git a/src/forms/project/docker_image.rs b/src/forms/project/docker_image.rs
index e9737d0b..a018174e 100644
--- a/src/forms/project/docker_image.rs
+++ b/src/forms/project/docker_image.rs
@@ -41,7 +41,7 @@ impl fmt::Display for DockerImage {
 impl DockerImage {
     #[tracing::instrument(name = "is_active")]
     pub async fn is_active(&self) -> Result<bool, String> {
-        DockerHub::from(self).is_active().await
+        DockerHub::try_from(self)?.is_active().await
     }
 }
 
diff --git a/src/helpers/dockerhub.rs b/src/helpers/dockerhub.rs
index 60ace373..7309cda2 100644
--- a/src/helpers/dockerhub.rs
+++ b/src/helpers/dockerhub.rs
@@ -114,6 +114,7 @@ pub struct OfficialRepoResult {
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Validate)]
 pub struct DockerHub<'a> {
     pub(crate) creds: DockerHubCreds<'a>,
+    #[validate(pattern = r"^[^:]+(:[^:]*)?$")]
     pub(crate) repos: String,
     pub(crate) image: String,
     pub(crate) tag: Option<String>,
@@ -260,20 +261,22 @@ impl<'a> DockerHub<'a> {
     pub async fn is_active(&'a mut self) -> Result<bool, String> {
         // if namespace/user is not set change endpoint and return a different response
 
-        // let repo = self.repos.clone();
-        // if self.repos.contains(':') {
-        //         let _repo = repo.split(':')
-        //         .collect::<Vec<&str>>();
-        //     self.repos = _repo.first().expect("split error").to_string();
-        //     self.tag = Some(_repo.last().expect("split error").to_string());
-        // }
-
-        // let n = self.repos.split(':').collect::<Vec<&str>>();
-        // if let Some(name: &str) == n.first() {
-        //     self.repos = name.to_string();
-        // }
-        // if let Some(name: &str) == n.last() {
-        //     self.tag = name.to_string();
+        // let n = self.repos
+        //     .split(':')
+        //     .map(|x| x.to_string())
+        //     .collect::<Vec<String>>();
+        //
+        // match n.len() {
+        //     1 => {
+        //         self.repos = n.first().unwrap().into();
+        //     }
+        //     2 => {
+        //         self.repos = n.first().unwrap().to_string();
+        //         self.tag = n.last().map(|s| s.to_string());
+        //     }
+        //     _ => {
+        //         return Err(format!("Wrong format of repository name"));
+        //     }
         // }
 
         if self.creds.username.is_empty() {
@@ -319,8 +322,12 @@ impl<'a> DockerHub<'a> {
     }
 }
 
-impl<'a> From<&'a DockerImage> for DockerHub<'a> {
-    fn from(image: &'a DockerImage) -> Self {
+
+impl<'a> TryFrom<&'a DockerImage> for DockerHub<'a> {
+    type Error = String;
+
+    fn try_from(image: &'a DockerImage) -> Result<Self, Self::Error> {
+
         let username = match image.dockerhub_user {
             Some(ref username) => username,
             None => "",
@@ -331,24 +338,45 @@ impl<'a> From<&'a DockerImage> for DockerHub<'a> {
         };
 
         let name = image.dockerhub_name.clone().unwrap_or("".to_string());
-        let mut tag = "".to_string();
-
-        if name.contains(':') {
-            let parts = name
-                .split(':')
-                .collect::<Vec<&str>>();
-            let name = parts.first().expect("Could not split").to_string();
-            tag = parts.last().expect("Could not split").to_string();
-        }
+        let n = name
+            .split(':')
+            .map(|x| x.to_string())
+            .collect::<Vec<String>>();
 
-        DockerHub {
+        let (name, tag) = match n.len() {
+            1 => {
+                (
+                    n.first().unwrap().into(),
+                    Some("".to_string())
+                )
+            }
+            2 => {
+                (
+                    n.first().unwrap().to_string(),
+                    n.last().map(|s| s.to_string())
+                )
+            }
+            _ => {
+                return Err(format!("Wrong format of repository name"));
+            }
+        };
+
+        let hub = DockerHub {
             creds: DockerHubCreds {
                 username: username,
                 password: password,
             },
             repos: name,
             image: format!("{}", image),
-            tag: Some(tag),
+            tag: tag,
+        };
+
+        if hub.validate().is_ok() {
+           Ok(hub)
+        } else {
+            let errors = hub.validate().unwrap_err();
+            tracing::debug!("DockerHub image properties are not valid {:?}", errors);
+            return Err(format!("{:?}", errors));
         }
     }
-}
+}
\ No newline at end of file
diff --git a/src/routes/project/update.rs b/src/routes/project/update.rs
index a6a8a8c9..0303d5cd 100644
--- a/src/routes/project/update.rs
+++ b/src/routes/project/update.rs
@@ -34,6 +34,7 @@ pub async fn item(
     // @todo ACL
     let form: ProjectForm= serde_json::from_value(request_json.clone())
         .map_err(|err| JsonResponse::bad_request(err.to_string()))?;
+
     if !form.validate().is_ok() {
         let errors = form.validate().unwrap_err();
         return Err(JsonResponse::bad_request(errors.to_string()));
@@ -41,11 +42,25 @@ pub async fn item(
 
     let project_name = form.custom.custom_stack_code.clone();
 
-    if let Ok(result) = form.is_readable_docker_image().await {
-        if false == result.readable {
+    // if let Ok(result) = form.is_readable_docker_image().await {
+    //     if false == result.readable {
+    //         return Err(JsonResponse::<DockerImageReadResult>::build()
+    //             .set_item(result)
+    //             .bad_request("Can not access docker image"));
+    //     }
+    // }
+
+    match form.is_readable_docker_image().await {
+        Ok(result) => {
+            if false == result.readable {
+                return Err(JsonResponse::<DockerImageReadResult>::build()
+                           .set_item(result)
+                           .bad_request("Can not access docker image"));
+            }
+        }
+        Err(e) => {
             return Err(JsonResponse::<DockerImageReadResult>::build()
-                .set_item(result)
-                .bad_request("Can not access docker image"));
+                .bad_request(e));
         }
     }
 

From 869ce5422c52623146a1f72c2f65caaa3c28f325 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Wed, 10 Apr 2024 12:22:58 +0300
Subject: [PATCH 258/284] remove extra structure

---
 src/helpers/dockerhub.rs                  | 73 +++++++++--------------
 src/routes/project/update.rs              |  8 ---
 tests/mock_data/custom-stack-payload.json |  2 +-
 3 files changed, 29 insertions(+), 54 deletions(-)

diff --git a/src/helpers/dockerhub.rs b/src/helpers/dockerhub.rs
index 7309cda2..fda20f0f 100644
--- a/src/helpers/dockerhub.rs
+++ b/src/helpers/dockerhub.rs
@@ -32,23 +32,24 @@ struct Image {
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 struct Tag {
-    content_type: String,
-    creator: i64,
-    digest: Option<String>,
-    full_size: i64,
-    id: i64,
-    images: Vec<Image>,
-    last_updated: String,
-    last_updater: i64,
-    last_updater_username: String,
-    media_type: String,
-    name: String,
-    repository: i64,
-    tag_last_pulled: Option<String>,
-    tag_last_pushed: Option<String>,
-    tag_status: String,
-    v2: bool,
+    pub content_type: String,
+    pub creator: i64,
+    pub digest: Option<String>,
+    pub full_size: i64,
+    pub id: i64,
+    pub images: Vec<Image>,
+    pub last_updated: String,
+    pub last_updater: i64,
+    pub last_updater_username: String,
+    pub media_type: String,
+    pub name: String,
+    pub repository: i64,
+    pub tag_last_pulled: Option<String>,
+    pub tag_last_pushed: Option<String>,
+    pub tag_status: String,
+    pub v2: bool,
 }
+
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 struct TagResult {
     pub count: Option<i64>,
@@ -70,7 +71,7 @@ pub struct OfficialRepoResults {
     pub count: Option<i64>,
     pub next: Option<Value>,
     pub previous: Option<Value>,
-    pub results: Vec<OfficialRepoResult>,
+    pub results: Vec<Tag>,
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
@@ -92,29 +93,11 @@ pub struct RepoResult {
 }
 
 
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct OfficialRepoResult {
-    pub images: Vec<Image>,
-    pub last_updated: String,
-    pub last_updater: i64,
-    pub content_type: String,
-    pub creator: i64,
-    pub digest: Option<String>,
-    pub full_size: i64,
-    pub id: i64,
-    pub last_updater_username: String,
-    pub media_type: String,
-    pub name: String,
-    pub repository: i64,
-    pub tag_last_pulled: Option<String>,
-    pub tag_last_pushed: Option<String>,
-    pub tag_status: String,
-    pub v2: bool,
-}
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Validate)]
 pub struct DockerHub<'a> {
     pub(crate) creds: DockerHubCreds<'a>,
-    #[validate(pattern = r"^[^:]+(:[^:]*)?$")]
+    //#[validate(pattern = r"^[^:]+(:[^:]*)?$")]
+    #[validate(pattern = r"^([a-z-_0-9]+)(:[a-z-_0-9\.]+)?$")]
     pub(crate) repos: String,
     pub(crate) image: String,
     pub(crate) tag: Option<String>,
@@ -213,7 +196,7 @@ impl<'a> DockerHub<'a> {
                                 false
                             }
                         });
-                    tracing::debug!("✅ result is {:?}", result);
+                    tracing::debug!("✅ search official repos result is {:?}", result);
                     result
                 } else {
                     tracing::debug!("🟥 Image tag is not active");
@@ -357,7 +340,7 @@ impl<'a> TryFrom<&'a DockerImage> for DockerHub<'a> {
                 )
             }
             _ => {
-                return Err(format!("Wrong format of repository name"));
+                return Err("Wrong format of repository name".to_owned());
             }
         };
 
@@ -371,12 +354,12 @@ impl<'a> TryFrom<&'a DockerImage> for DockerHub<'a> {
             tag: tag,
         };
 
-        if hub.validate().is_ok() {
-           Ok(hub)
-        } else {
-            let errors = hub.validate().unwrap_err();
-            tracing::debug!("DockerHub image properties are not valid {:?}", errors);
-            return Err(format!("{:?}", errors));
+        if let Err(errors) = hub.validate() {
+            let msg = "DockerHub image properties are not valid. Please verify repository name";
+            tracing::debug!("{:?} {:?}", msg, errors);
+            return Err(format!("{:?}", msg));
         }
+
+       Ok(hub)
     }
 }
\ No newline at end of file
diff --git a/src/routes/project/update.rs b/src/routes/project/update.rs
index 0303d5cd..38d08ea5 100644
--- a/src/routes/project/update.rs
+++ b/src/routes/project/update.rs
@@ -42,14 +42,6 @@ pub async fn item(
 
     let project_name = form.custom.custom_stack_code.clone();
 
-    // if let Ok(result) = form.is_readable_docker_image().await {
-    //     if false == result.readable {
-    //         return Err(JsonResponse::<DockerImageReadResult>::build()
-    //             .set_item(result)
-    //             .bad_request("Can not access docker image"));
-    //     }
-    // }
-
     match form.is_readable_docker_image().await {
         Ok(result) => {
             if false == result.readable {
diff --git a/tests/mock_data/custom-stack-payload.json b/tests/mock_data/custom-stack-payload.json
index 88fe2c25..f602dbbd 100644
--- a/tests/mock_data/custom-stack-payload.json
+++ b/tests/mock_data/custom-stack-payload.json
@@ -1 +1 @@
-{"commonDomain":"","domainList":{},"region":"","zone":null,"server":"cx11","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":true,"disk_type":"pd-standart","cloud_token":"*******************","cloud_key":"*********","cloud_secret":"****************","disk_size":40,"provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-annually","custom":{"networks":[{"name":"default_network","id":"lsnd2ttg3ivhw3db5"}],"web":[{"_etag":null,"_id":"lsq2tdff3c0acf2lj","_created":"2023-04-28T09:46:19.470502","_updated":"2024-02-09T13:44:36.854036","name":"PostgREST","code":"postgrest","role":["postgrest"],"type":"web","default":false,"popularity":null,"descr":null,"ports":{"public":["3000","8080"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":null,"category_id":null,"parent_app_id":null,"full_description":null,"description":null,"plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.0","ram_size":null,"disk_size":null,"dockerhub_image":"","form":null,"requirements":null,"docker_image_is_internal":true,"custom_preset":{"volumes":[],"environment":[{"key":"PGRST_OPENAPI_SERVER_PROXY_URI","value":"http://DOMAIN_NAME:3000"},{"key":"PGRST_DB_ANON_ROLE","value":"anon"},{"key":"PGRST_DB_SCHEMA","value":"public"},{"key":"PGHOST","value":"postgresql_host"},{"key":"PGPORT","value":"5432"},{"key":"PGUSER","value":"user"},{"key":"PGDATABASE","value":"database"},{"key":"PGPASSWORD ","value":"password"}],"shared_ports":[{"host_port":"3000","container_port":"3000"}],"restart":"always"},"category":[null],"group":[],"versions":[{"_id":556,"name":"Postgrest","version":"latest","update_status":"published","tag":"latest"}],"links":[{"url":"https://postgrest.org","title":"Vendor","type":"vendor","follow":false},{"repo_owner":"PostgREST","repo_name":"postgrest","type":"github","follow":false}],"version":{"_id":556,"name":"Postgrest","version":"latest","update_status":"published","tag":"latest"},"network":["lsnd2ttg3ivhw3db5"],"restart":"always","timestamp":"2024-02-17T12:50:01.659Z","volumes":[],"environment":[{"key":"PGRST_OPENAPI_SERVER_PROXY_URI","value":"http://DOMAIN_NAME:3000"},{"key":"PGRST_DB_ANON_ROLE","value":"anon"},{"key":"PGRST_DB_SCHEMA","value":"public"},{"key":"PGHOST","value":"postgresql_host"},{"key":"PGPORT","value":"5432"},{"key":"PGUSER","value":"user"},{"key":"PGDATABASE","value":"database"},{"key":"PGPASSWORD ","value":"password"}],"shared_ports":[{"host_port":"3000","container_port":"3000"}],"domain":"","dockerhub_name":"mariadb:11.3.2"}],"feature":[],"service":[],"servers_count":3,"project_name":"Databases","custom_stack_code":"databases"}}
\ No newline at end of file
+{"commonDomain":"","domainList":{},"region":"","zone":null,"server":"cx11","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":true,"disk_type":"pd-standart","cloud_token":"*******************","cloud_key":"*********","cloud_secret":"****************","disk_size":40,"provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-annually","custom":{"networks":[{"name":"default_network","id":"lsnd2ttg3ivhw3db5"}],"web":[{"_etag":null,"_id":"lsq2tdff3c0acf2lj","_created":"2023-04-28T09:46:19.470502","_updated":"2024-02-09T13:44:36.854036","name":"PostgREST","code":"postgrest","role":["postgrest"],"type":"web","default":false,"popularity":null,"descr":null,"ports":{"public":["3000","8080"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":null,"category_id":null,"parent_app_id":null,"full_description":null,"description":null,"plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.0","ram_size":null,"disk_size":null,"dockerhub_image":"","form":null,"requirements":null,"docker_image_is_internal":true,"custom_preset":{"volumes":[],"environment":[{"key":"PGRST_OPENAPI_SERVER_PROXY_URI","value":"http://DOMAIN_NAME:3000"},{"key":"PGRST_DB_ANON_ROLE","value":"anon"},{"key":"PGRST_DB_SCHEMA","value":"public"},{"key":"PGHOST","value":"postgresql_host"},{"key":"PGPORT","value":"5432"},{"key":"PGUSER","value":"user"},{"key":"PGDATABASE","value":"database"},{"key":"PGPASSWORD ","value":"password"}],"shared_ports":[{"host_port":"3000","container_port":"3000"}],"restart":"always"},"category":[null],"group":[],"versions":[{"_id":556,"name":"Postgrest","version":"latest","update_status":"published","tag":"latest"}],"links":[{"url":"https://postgrest.org","title":"Vendor","type":"vendor","follow":false},{"repo_owner":"PostgREST","repo_name":"postgrest","type":"github","follow":false}],"version":{"_id":556,"name":"Postgrest","version":"latest","update_status":"published","tag":"latest"},"network":["lsnd2ttg3ivhw3db5"],"restart":"always","timestamp":"2024-02-17T12:50:01.659Z","volumes":[],"environment":[{"key":"PGRST_OPENAPI_SERVER_PROXY_URI","value":"http://DOMAIN_NAME:3000"},{"key":"PGRST_DB_ANON_ROLE","value":"anon"},{"key":"PGRST_DB_SCHEMA","value":"public"},{"key":"PGHOST","value":"postgresql_host"},{"key":"PGPORT","value":"5432"},{"key":"PGUSER","value":"user"},{"key":"PGDATABASE","value":"database"},{"key":"PGPASSWORD ","value":"password"}],"shared_ports":[{"host_port":"3000","container_port":"3000"}],"domain":"","dockerhub_name":"mariadb:11.3.2-latest"}],"feature":[],"service":[],"servers_count":3,"project_name":"Databases","custom_stack_code":"databases"}}
\ No newline at end of file

From 83b9079c9b0f90bc7567f29aa036b9efe141f1a6 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Thu, 11 Apr 2024 18:26:18 +0300
Subject: [PATCH 259/284] 47

---
 src/forms/mod.rs                       |  3 +-
 src/forms/{rating.rs => rating/add.rs} |  4 +--
 src/forms/rating/mod.rs                |  5 ++++
 src/forms/rating/useredit.rs           | 12 ++++++++
 src/routes/rating/add.rs               |  2 +-
 src/routes/rating/edit.rs              | 38 ++++++++++++++++++++++++++
 src/routes/rating/mod.rs               |  2 ++
 src/startup.rs                         |  3 +-
 8 files changed, 63 insertions(+), 6 deletions(-)
 rename src/forms/{rating.rs => rating/add.rs} (92%)
 create mode 100644 src/forms/rating/mod.rs
 create mode 100644 src/forms/rating/useredit.rs
 create mode 100644 src/routes/rating/edit.rs

diff --git a/src/forms/mod.rs b/src/forms/mod.rs
index 06471817..a5651bfa 100644
--- a/src/forms/mod.rs
+++ b/src/forms/mod.rs
@@ -1,10 +1,9 @@
-mod rating;
+pub mod rating;
 pub mod project;
 pub mod user;
 pub(crate) mod cloud;
 pub(crate) mod server;
 
-pub use rating::*;
 pub use cloud::*;
 pub use server::*;
 pub use user::UserForm;
diff --git a/src/forms/rating.rs b/src/forms/rating/add.rs
similarity index 92%
rename from src/forms/rating.rs
rename to src/forms/rating/add.rs
index e3f82810..a2c90d2c 100644
--- a/src/forms/rating.rs
+++ b/src/forms/rating/add.rs
@@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
 use serde_valid::Validate;
 
 #[derive(Serialize, Deserialize, Debug, Validate)]
-pub struct Rating {
+pub struct AddRating {
     pub obj_id: i32,                    // product external id
     pub category: models::RateCategory, // rating of product | rating of service etc
     #[validate(max_length = 1000)]
@@ -13,7 +13,7 @@ pub struct Rating {
     pub rate: i32, //
 }
 
-impl Into<models::Rating> for Rating {
+impl Into<models::Rating> for AddRating {
     fn into(self) -> models::Rating {
         let mut rating = models::Rating::default();
         rating.obj_id = self.obj_id;
diff --git a/src/forms/rating/mod.rs b/src/forms/rating/mod.rs
new file mode 100644
index 00000000..ae8dbdf4
--- /dev/null
+++ b/src/forms/rating/mod.rs
@@ -0,0 +1,5 @@
+mod add;
+mod useredit;
+
+pub use add::AddRating as Add;
+pub use useredit::UserEditRating as UserEdit;
diff --git a/src/forms/rating/useredit.rs b/src/forms/rating/useredit.rs
new file mode 100644
index 00000000..668a1daf
--- /dev/null
+++ b/src/forms/rating/useredit.rs
@@ -0,0 +1,12 @@
+use crate::models;
+use serde::{Deserialize, Serialize};
+use serde_valid::Validate;
+
+#[derive(Serialize, Deserialize, Debug, Validate)]
+pub struct UserEditRating {
+    #[validate(max_length = 1000)]
+    pub comment: Option<String>, // always linked to a product
+    #[validate(minimum = 0)]
+    #[validate(maximum = 10)]
+    pub rate: i32, //
+}
diff --git a/src/routes/rating/add.rs b/src/routes/rating/add.rs
index e91dbe6a..34d8c505 100644
--- a/src/routes/rating/add.rs
+++ b/src/routes/rating/add.rs
@@ -15,7 +15,7 @@ use std::sync::Arc;
 #[post("")]
 pub async fn add_handler(
     user: web::ReqData<Arc<models::User>>,
-    form: web::Json<forms::Rating>,
+    form: web::Json<forms::rating::Add>,
     pg_pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
     let _product = db::product::fetch_by_obj(pg_pool.get_ref(), form.obj_id)
diff --git a/src/routes/rating/edit.rs b/src/routes/rating/edit.rs
new file mode 100644
index 00000000..e8ca98a9
--- /dev/null
+++ b/src/routes/rating/edit.rs
@@ -0,0 +1,38 @@
+use crate::forms;
+use crate::helpers::JsonResponse;
+use crate::models;
+use crate::db;
+use actix_web::{put, web, Responder, Result};
+use sqlx::PgPool;
+use std::sync::Arc;
+
+// workflow
+// add, update, list, get(user_id), ACL,
+// ACL - access to func for a user
+// ACL - access to objects for a user
+
+#[tracing::instrument(name = "User edit rating.")]
+#[put("")]
+pub async fn user_edit_handler(
+    path: web::Path<(i32,)>,
+    user: web::ReqData<Arc<models::User>>,
+    form: web::Json<forms::rating::UserEdit>,
+    pg_pool: web::Data<PgPool>,
+) -> Result<impl Responder> {
+    let rate_id = path.0;
+    let rating = db::rating::fetch(pg_pool.get_ref(), rate_id)
+        .await
+        .map_err(|_err| JsonResponse::<models::Rating>::build().internal_server_error(""))
+        .and_then(|rating| {
+            match rating {
+                Some(rating) => { Ok(rating) },
+                None => Err(JsonResponse::<models::Rating>::build().not_found("not found"))
+            }
+        })?;
+
+    //todo check if form is valid
+    //todo add update_model function to form
+    //todo add the db saving of the model
+
+    Ok(JsonResponse::build().set_item(rating).ok("OK"))
+}
diff --git a/src/routes/rating/mod.rs b/src/routes/rating/mod.rs
index b13668a0..c654b782 100644
--- a/src/routes/rating/mod.rs
+++ b/src/routes/rating/mod.rs
@@ -1,5 +1,7 @@
 pub mod add;
 pub mod get;
+mod edit;
 
 pub use add::*;
 pub use get::*;
+pub use edit::*;
diff --git a/src/startup.rs b/src/startup.rs
index e199afab..814ca913 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -62,7 +62,8 @@ pub async fn run(
                 web::scope("/rating")
                     .service(routes::rating::add_handler)
                     .service(routes::rating::get_handler)
-                    .service(routes::rating::list_handler),
+                    .service(routes::rating::list_handler)
+                    .service(routes::rating::user_edit_handler),
             )
             .service(
                 web::scope("/project")

From 15744042843c8fe443e8438b2ae378ff0091fae0 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Fri, 12 Apr 2024 11:54:35 +0300
Subject: [PATCH 260/284] social field should be optional

---
 src/forms/user.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/forms/user.rs b/src/forms/user.rs
index 0c58d75f..9009c7ac 100644
--- a/src/forms/user.rs
+++ b/src/forms/user.rs
@@ -25,7 +25,7 @@ pub struct User {
     pub email: String,
     #[serde(rename = "email_confirmed")]
     pub email_confirmed: bool,
-    pub social: bool,
+    pub social: Option<bool>,
     pub website: Option<String>,
     pub currency: Value,
     pub phone: Option<String>,

From b7b3db518f344c4ff0b20e05f6a428a588095a8a Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Fri, 12 Apr 2024 12:06:30 +0300
Subject: [PATCH 261/284] cleanup

---
 src/forms/user.rs | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/src/forms/user.rs b/src/forms/user.rs
index 9009c7ac..5cf67358 100644
--- a/src/forms/user.rs
+++ b/src/forms/user.rs
@@ -129,9 +129,6 @@ pub struct SuspensionHints {
 impl TryInto<UserModel> for UserForm {
     type Error = String;
     fn try_into(self) -> Result<UserModel, Self::Error> {
-        // let id = self.id.parse::<i32>().map_err(
-        //     |msg| { format!("{:?}", msg) }
-        // )?;
         Ok(UserModel {
             id: self.user.id,
             first_name: self.user.first_name.unwrap_or("Noname".to_string()),

From dba85f876720d272a28c6f700b7fa9b2ffc394a3 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Fri, 12 Apr 2024 16:57:52 +0300
Subject: [PATCH 262/284] 47 validate rating when added

---
 src/routes/rating/add.rs | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/routes/rating/add.rs b/src/routes/rating/add.rs
index 34d8c505..7770eaad 100644
--- a/src/routes/rating/add.rs
+++ b/src/routes/rating/add.rs
@@ -5,6 +5,7 @@ use crate::db;
 use actix_web::{post, web, Responder, Result};
 use sqlx::PgPool;
 use std::sync::Arc;
+use serde_valid::Validate;
 
 // workflow
 // add, update, list, get(user_id), ACL,
@@ -18,6 +19,10 @@ pub async fn add_handler(
     form: web::Json<forms::rating::Add>,
     pg_pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
+    if let Err(errors) = form.validate() {
+        return Err(JsonResponse::<models::Rating>::build().form_error(errors.to_string()));
+    }
+
     let _product = db::product::fetch_by_obj(pg_pool.get_ref(), form.obj_id)
         .await
         .map_err(|_msg| JsonResponse::<models::Rating>::build().internal_server_error(_msg))?

From 2c189c92f05570588c5c49f10ab5029d33d277f5 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Fri, 12 Apr 2024 17:31:09 +0300
Subject: [PATCH 263/284] 47 casbin rule for user edit rating

---
 .../20240412141011_casbin_user_rating_edit.down.sql   |  3 +++
 .../20240412141011_casbin_user_rating_edit.up.sql     |  5 +++++
 src/routes/rating/edit.rs                             | 11 ++++++++---
 3 files changed, 16 insertions(+), 3 deletions(-)
 create mode 100644 migrations/20240412141011_casbin_user_rating_edit.down.sql
 create mode 100644 migrations/20240412141011_casbin_user_rating_edit.up.sql

diff --git a/migrations/20240412141011_casbin_user_rating_edit.down.sql b/migrations/20240412141011_casbin_user_rating_edit.down.sql
new file mode 100644
index 00000000..66634ccb
--- /dev/null
+++ b/migrations/20240412141011_casbin_user_rating_edit.down.sql
@@ -0,0 +1,3 @@
+-- Add down migration script here
+DELETE FROM casbin_rule
+WHERE ptype = 'p' and v0 = 'group_user' and v1 = '/rating/:id' and v2 = 'PUT';
diff --git a/migrations/20240412141011_casbin_user_rating_edit.up.sql b/migrations/20240412141011_casbin_user_rating_edit.up.sql
new file mode 100644
index 00000000..79d4b19f
--- /dev/null
+++ b/migrations/20240412141011_casbin_user_rating_edit.up.sql
@@ -0,0 +1,5 @@
+-- Add up migration script here
+INSERT INTO casbin_rule
+(id, ptype, v0, v1, v2, v3, v4, v5)
+VALUES((select max(id) + 1 from casbin_rule cr), 'p', 'group_user', '/rating/:id', 'PUT', '', '', '');
+
diff --git a/src/routes/rating/edit.rs b/src/routes/rating/edit.rs
index e8ca98a9..69399e2d 100644
--- a/src/routes/rating/edit.rs
+++ b/src/routes/rating/edit.rs
@@ -5,6 +5,7 @@ use crate::db;
 use actix_web::{put, web, Responder, Result};
 use sqlx::PgPool;
 use std::sync::Arc;
+use serde_valid::Validate;
 
 // workflow
 // add, update, list, get(user_id), ACL,
@@ -12,25 +13,29 @@ use std::sync::Arc;
 // ACL - access to objects for a user
 
 #[tracing::instrument(name = "User edit rating.")]
-#[put("")]
+#[put("/{id}")]
 pub async fn user_edit_handler(
     path: web::Path<(i32,)>,
     user: web::ReqData<Arc<models::User>>,
     form: web::Json<forms::rating::UserEdit>,
     pg_pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
+    if let Err(errors) = form.validate() {
+        return Err(JsonResponse::<models::Rating>::build().form_error(errors.to_string()));
+    }
+
     let rate_id = path.0;
     let rating = db::rating::fetch(pg_pool.get_ref(), rate_id)
         .await
         .map_err(|_err| JsonResponse::<models::Rating>::build().internal_server_error(""))
         .and_then(|rating| {
             match rating {
-                Some(rating) => { Ok(rating) },
+                Some(rating) if rating.user_id != user.id => Err(JsonResponse::<models::Rating>::build().not_found("not found")),
+                Some(rating) =>  Ok(rating),
                 None => Err(JsonResponse::<models::Rating>::build().not_found("not found"))
             }
         })?;
 
-    //todo check if form is valid
     //todo add update_model function to form
     //todo add the db saving of the model
 

From ef67676c33b86a1b8524a4d95148b50253e25216 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Fri, 12 Apr 2024 17:50:06 +0300
Subject: [PATCH 264/284] 47 edit rating logic

---
 src/db/rating.rs             | 28 ++++++++++++++++++++++++++++
 src/forms/rating/useredit.rs | 15 ++++++++++++++-
 src/routes/rating/edit.rs    | 17 +++++++++++++----
 3 files changed, 55 insertions(+), 5 deletions(-)

diff --git a/src/db/rating.rs b/src/db/rating.rs
index 599c49cc..114d47dd 100644
--- a/src/db/rating.rs
+++ b/src/db/rating.rs
@@ -131,3 +131,31 @@ pub async fn insert(pool: &PgPool, mut rating: models::Rating) -> Result<models:
         "Failed to insert".to_string()
     })
 }
+
+pub async fn update(pool: &PgPool, rating: models::Rating) -> Result<models::Rating, String> {
+    let query_span = tracing::info_span!("Updating rating into the database");
+    sqlx::query!(
+        r#"
+        UPDATE rating
+        SET 
+            comment=$1,
+            rate=$2,
+            updated_at=NOW() at time zone 'utc'
+        WHERE id = $3
+        "#,
+        rating.comment,
+        rating.rate,
+        rating.id
+    )
+    .execute(pool)
+    .instrument(query_span)
+    .await
+    .map(|_|{
+        tracing::info!("Rating {} has been saved to the database", rating.id);
+        rating
+    })
+    .map_err(|err| {
+        tracing::error!("Failed to execute query: {:?}", err);
+        "".to_string()
+    })
+}
diff --git a/src/forms/rating/useredit.rs b/src/forms/rating/useredit.rs
index 668a1daf..4f5ae02f 100644
--- a/src/forms/rating/useredit.rs
+++ b/src/forms/rating/useredit.rs
@@ -8,5 +8,18 @@ pub struct UserEditRating {
     pub comment: Option<String>, // always linked to a product
     #[validate(minimum = 0)]
     #[validate(maximum = 10)]
-    pub rate: i32, //
+    pub rate: Option<i32>, //
+}
+
+impl UserEditRating {
+    pub fn update(self, rating: &mut models::Rating) 
+    {
+        if let Some(comment) = self.comment {
+            rating.comment.insert(comment);
+        }
+
+        if let Some(rate) = self.rate {
+            rating.rate.insert(rate);
+        }
+    }
 }
diff --git a/src/routes/rating/edit.rs b/src/routes/rating/edit.rs
index 69399e2d..90b1d4a1 100644
--- a/src/routes/rating/edit.rs
+++ b/src/routes/rating/edit.rs
@@ -25,7 +25,7 @@ pub async fn user_edit_handler(
     }
 
     let rate_id = path.0;
-    let rating = db::rating::fetch(pg_pool.get_ref(), rate_id)
+    let mut rating = db::rating::fetch(pg_pool.get_ref(), rate_id)
         .await
         .map_err(|_err| JsonResponse::<models::Rating>::build().internal_server_error(""))
         .and_then(|rating| {
@@ -36,8 +36,17 @@ pub async fn user_edit_handler(
             }
         })?;
 
-    //todo add update_model function to form
-    //todo add the db saving of the model
+    form.into_inner().update(&mut rating);
 
-    Ok(JsonResponse::build().set_item(rating).ok("OK"))
+    db::rating::update(pg_pool.get_ref(), rating)
+        .await
+        .map(|rating| {
+            JsonResponse::<models::Rating>::build()
+                .set_item(rating)
+                .ok("success")
+        })
+        .map_err(|err| {
+            tracing::error!("Failed to execute query: {:?}", err);
+            JsonResponse::<models::Rating>::build().internal_server_error("Rating not update")
+        })
 }

From dbe6b9a76a1ab36347c6f1de78f7ef997af10495 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Fri, 12 Apr 2024 21:23:17 +0300
Subject: [PATCH 265/284] 47 show only visible

---
 src/db/rating.rs         | 32 +++++++++++++++++++++++++++++++-
 src/routes/rating/add.rs |  2 +-
 src/routes/rating/get.rs |  6 +++---
 src/startup.rs           |  6 +++---
 4 files changed, 38 insertions(+), 8 deletions(-)

diff --git a/src/db/rating.rs b/src/db/rating.rs
index 114d47dd..e56a7473 100644
--- a/src/db/rating.rs
+++ b/src/db/rating.rs
@@ -16,7 +16,9 @@ pub async fn fetch_all(pool: &PgPool) -> Result<Vec<models::Rating>, String> {
             rate,
             created_at,
             updated_at
-        FROM rating"#
+        FROM rating
+        ORDER BY id DESC
+        "#
     )
     .fetch_all(pool)
     .instrument(query_span)
@@ -159,3 +161,31 @@ pub async fn update(pool: &PgPool, rating: models::Rating) -> Result<models::Rat
         "".to_string()
     })
 }
+
+pub async fn fetch_all_visible(pool: &PgPool) -> Result<Vec<models::Rating>, String> {
+    let query_span = tracing::info_span!("Fetch all ratings.");
+    sqlx::query_as!(
+        models::Rating,
+        r#"SELECT 
+            id,
+            user_id,
+            obj_id,
+            category as "category: _",
+            comment,
+            hidden,
+            rate,
+            created_at,
+            updated_at
+        FROM rating
+        WHERE hidden = false 
+        ORDER BY id DESC
+        "#,
+    )
+    .fetch_all(pool)
+    .instrument(query_span)
+    .await
+    .map_err(|e| {
+        tracing::error!("Failed to execute fetch query: {:?}", e);
+        "".to_string()
+    })
+}
diff --git a/src/routes/rating/add.rs b/src/routes/rating/add.rs
index 7770eaad..ff7de887 100644
--- a/src/routes/rating/add.rs
+++ b/src/routes/rating/add.rs
@@ -14,7 +14,7 @@ use serde_valid::Validate;
 
 #[tracing::instrument(name = "Add rating.")]
 #[post("")]
-pub async fn add_handler(
+pub async fn user_add_handler(
     user: web::ReqData<Arc<models::User>>,
     form: web::Json<forms::rating::Add>,
     pg_pool: web::Data<PgPool>,
diff --git a/src/routes/rating/get.rs b/src/routes/rating/get.rs
index 46c138a4..f6edc843 100644
--- a/src/routes/rating/get.rs
+++ b/src/routes/rating/get.rs
@@ -6,7 +6,7 @@ use sqlx::PgPool;
 
 #[tracing::instrument(name = "Get rating.")]
 #[get("/{id}")]
-pub async fn get_handler(
+pub async fn anonymous_get_handler(
     path: web::Path<(i32,)>,
     pg_pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
@@ -26,11 +26,11 @@ pub async fn get_handler(
 
 #[tracing::instrument(name = "Get all ratings.")]
 #[get("")]
-pub async fn list_handler(
+pub async fn anonymous_list_handler(
     path: web::Path<()>,
     pg_pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
-    db::rating::fetch_all(pg_pool.get_ref())
+    db::rating::fetch_all_visible(pg_pool.get_ref())
         .await
         .map(|ratings| JsonResponse::build().set_list(ratings).ok("OK"))
         .map_err(|_err| JsonResponse::<models::Rating>::build().internal_server_error(""))
diff --git a/src/startup.rs b/src/startup.rs
index 814ca913..8868858f 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -60,9 +60,9 @@ pub async fn run(
             )
             .service(
                 web::scope("/rating")
-                    .service(routes::rating::add_handler)
-                    .service(routes::rating::get_handler)
-                    .service(routes::rating::list_handler)
+                    .service(routes::rating::user_add_handler)
+                    .service(routes::rating::anonymous_get_handler)
+                    .service(routes::rating::anonymous_list_handler)
                     .service(routes::rating::user_edit_handler),
             )
             .service(

From 4758398d6ee52b0b01ead63874e01a4137ce2e0f Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Fri, 12 Apr 2024 21:29:30 +0300
Subject: [PATCH 266/284] 47 anonymous_get visible

---
 src/routes/rating/edit.rs | 5 ++---
 src/routes/rating/get.rs  | 4 ++--
 2 files changed, 4 insertions(+), 5 deletions(-)

diff --git a/src/routes/rating/edit.rs b/src/routes/rating/edit.rs
index 90b1d4a1..06a65a84 100644
--- a/src/routes/rating/edit.rs
+++ b/src/routes/rating/edit.rs
@@ -30,9 +30,8 @@ pub async fn user_edit_handler(
         .map_err(|_err| JsonResponse::<models::Rating>::build().internal_server_error(""))
         .and_then(|rating| {
             match rating {
-                Some(rating) if rating.user_id != user.id => Err(JsonResponse::<models::Rating>::build().not_found("not found")),
-                Some(rating) =>  Ok(rating),
-                None => Err(JsonResponse::<models::Rating>::build().not_found("not found"))
+                Some(rating) if rating.user_id == user.id=>  Ok(rating),
+                _ => Err(JsonResponse::<models::Rating>::build().not_found("not found"))
             }
         })?;
 
diff --git a/src/routes/rating/get.rs b/src/routes/rating/get.rs
index f6edc843..3e7d9580 100644
--- a/src/routes/rating/get.rs
+++ b/src/routes/rating/get.rs
@@ -16,8 +16,8 @@ pub async fn anonymous_get_handler(
         .map_err(|_err| JsonResponse::<models::Rating>::build().internal_server_error(""))
         .and_then(|rating| {
             match rating {
-                Some(rating) => { Ok(rating) },
-                None => Err(JsonResponse::<models::Rating>::build().not_found("not found"))
+                Some(rating) if rating.hidden == Some(false) => { Ok(rating) },
+                _ => Err(JsonResponse::<models::Rating>::build().not_found("not found"))
             }
         })?;
 

From 1390f700c643d66a1735e291a1deec4ce2ac1476 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Fri, 12 Apr 2024 21:38:56 +0300
Subject: [PATCH 267/284] 47 admin_get_rating

---
 ...412141011_casbin_user_rating_edit.down.sql |  3 +++
 ...40412141011_casbin_user_rating_edit.up.sql |  3 +++
 src/routes/rating/get.rs                      | 24 +++++++++++++++++--
 src/startup.rs                                |  4 ++++
 4 files changed, 32 insertions(+), 2 deletions(-)

diff --git a/migrations/20240412141011_casbin_user_rating_edit.down.sql b/migrations/20240412141011_casbin_user_rating_edit.down.sql
index 66634ccb..24fb9435 100644
--- a/migrations/20240412141011_casbin_user_rating_edit.down.sql
+++ b/migrations/20240412141011_casbin_user_rating_edit.down.sql
@@ -1,3 +1,6 @@
 -- Add down migration script here
 DELETE FROM casbin_rule
 WHERE ptype = 'p' and v0 = 'group_user' and v1 = '/rating/:id' and v2 = 'PUT';
+
+DELETE FROM casbin_rule
+WHERE ptype = 'p' and v0 = 'group_admin' and v1 = '/admin/rating/:id' and v2 = 'GET';
diff --git a/migrations/20240412141011_casbin_user_rating_edit.up.sql b/migrations/20240412141011_casbin_user_rating_edit.up.sql
index 79d4b19f..845e59b7 100644
--- a/migrations/20240412141011_casbin_user_rating_edit.up.sql
+++ b/migrations/20240412141011_casbin_user_rating_edit.up.sql
@@ -3,3 +3,6 @@ INSERT INTO casbin_rule
 (id, ptype, v0, v1, v2, v3, v4, v5)
 VALUES((select max(id) + 1 from casbin_rule cr), 'p', 'group_user', '/rating/:id', 'PUT', '', '', '');
 
+INSERT INTO casbin_rule
+(id, ptype, v0, v1, v2, v3, v4, v5)
+VALUES((select max(id) + 1 from casbin_rule cr), 'p', 'group_admin', '/admin/rating/:id', 'GET', '', '', '');
diff --git a/src/routes/rating/get.rs b/src/routes/rating/get.rs
index 3e7d9580..4b54f20c 100644
--- a/src/routes/rating/get.rs
+++ b/src/routes/rating/get.rs
@@ -4,7 +4,7 @@ use crate::models;
 use actix_web::{get, web, Responder, Result};
 use sqlx::PgPool;
 
-#[tracing::instrument(name = "Get rating.")]
+#[tracing::instrument(name = "Anonymouse get rating.")]
 #[get("/{id}")]
 pub async fn anonymous_get_handler(
     path: web::Path<(i32,)>,
@@ -24,7 +24,7 @@ pub async fn anonymous_get_handler(
     Ok(JsonResponse::build().set_item(rating).ok("OK"))
 }
 
-#[tracing::instrument(name = "Get all ratings.")]
+#[tracing::instrument(name = "Anonymous get all ratings.")]
 #[get("")]
 pub async fn anonymous_list_handler(
     path: web::Path<()>,
@@ -35,3 +35,23 @@ pub async fn anonymous_list_handler(
         .map(|ratings| JsonResponse::build().set_list(ratings).ok("OK"))
         .map_err(|_err| JsonResponse::<models::Rating>::build().internal_server_error(""))
 }
+
+#[tracing::instrument(name = "Admin get rating.")]
+#[get("/{id}")]
+pub async fn admin_get_handler(
+    path: web::Path<(i32,)>,
+    pg_pool: web::Data<PgPool>,
+) -> Result<impl Responder> {
+    let rate_id = path.0;
+    let rating = db::rating::fetch(pg_pool.get_ref(), rate_id)
+        .await
+        .map_err(|_err| JsonResponse::<models::Rating>::build().internal_server_error(""))
+        .and_then(|rating| {
+            match rating {
+                Some(rating) => { Ok(rating) },
+                _ => Err(JsonResponse::<models::Rating>::build().not_found("not found"))
+            }
+        })?;
+
+    Ok(JsonResponse::build().set_item(rating).ok("OK"))
+}
diff --git a/src/startup.rs b/src/startup.rs
index 8868858f..0fb7d54d 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -78,6 +78,10 @@ pub async fn run(
             )
             .service(
                 web::scope("/admin")
+                    .service(
+                        web::scope("/rating")
+                            .service(routes::rating::admin_get_handler)
+                    )
                     .service(
                         web::scope("/project")
                             .service(crate::routes::project::get::admin_list)

From 842df597ea73a9e1184dd73b765352b3cc046a30 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Fri, 12 Apr 2024 21:46:38 +0300
Subject: [PATCH 268/284] 47 admin_list_handler, transaction

---
 .../20240412141011_casbin_user_rating_edit.down.sql  |  3 +++
 .../20240412141011_casbin_user_rating_edit.up.sql    |  8 ++++++++
 src/routes/rating/get.rs                             | 12 ++++++++++++
 src/startup.rs                                       |  1 +
 4 files changed, 24 insertions(+)

diff --git a/migrations/20240412141011_casbin_user_rating_edit.down.sql b/migrations/20240412141011_casbin_user_rating_edit.down.sql
index 24fb9435..a9a66790 100644
--- a/migrations/20240412141011_casbin_user_rating_edit.down.sql
+++ b/migrations/20240412141011_casbin_user_rating_edit.down.sql
@@ -4,3 +4,6 @@ WHERE ptype = 'p' and v0 = 'group_user' and v1 = '/rating/:id' and v2 = 'PUT';
 
 DELETE FROM casbin_rule
 WHERE ptype = 'p' and v0 = 'group_admin' and v1 = '/admin/rating/:id' and v2 = 'GET';
+
+DELETE FROM casbin_rule
+WHERE ptype = 'p' and v0 = 'group_admin' and v1 = '/admin/rating' and v2 = 'GET';
diff --git a/migrations/20240412141011_casbin_user_rating_edit.up.sql b/migrations/20240412141011_casbin_user_rating_edit.up.sql
index 845e59b7..ca44313d 100644
--- a/migrations/20240412141011_casbin_user_rating_edit.up.sql
+++ b/migrations/20240412141011_casbin_user_rating_edit.up.sql
@@ -1,4 +1,6 @@
 -- Add up migration script here
+BEGIN TRANSACTION;
+
 INSERT INTO casbin_rule
 (id, ptype, v0, v1, v2, v3, v4, v5)
 VALUES((select max(id) + 1 from casbin_rule cr), 'p', 'group_user', '/rating/:id', 'PUT', '', '', '');
@@ -6,3 +8,9 @@ VALUES((select max(id) + 1 from casbin_rule cr), 'p', 'group_user', '/rating/:id
 INSERT INTO casbin_rule
 (id, ptype, v0, v1, v2, v3, v4, v5)
 VALUES((select max(id) + 1 from casbin_rule cr), 'p', 'group_admin', '/admin/rating/:id', 'GET', '', '', '');
+
+INSERT INTO casbin_rule
+(id, ptype, v0, v1, v2, v3, v4, v5)
+VALUES((select max(id) + 1 from casbin_rule cr), 'p', 'group_admin', '/admin/rating', 'GET', '', '', '');
+
+COMMIT TRANSACTION;
diff --git a/src/routes/rating/get.rs b/src/routes/rating/get.rs
index 4b54f20c..1880b7be 100644
--- a/src/routes/rating/get.rs
+++ b/src/routes/rating/get.rs
@@ -55,3 +55,15 @@ pub async fn admin_get_handler(
 
     Ok(JsonResponse::build().set_item(rating).ok("OK"))
 }
+
+#[tracing::instrument(name = "Admin get the list of ratings.")]
+#[get("")]
+pub async fn admin_list_handler(
+    path: web::Path<()>,
+    pg_pool: web::Data<PgPool>,
+) -> Result<impl Responder> {
+    db::rating::fetch_all(pg_pool.get_ref())
+        .await
+        .map(|ratings| JsonResponse::build().set_list(ratings).ok("OK"))
+        .map_err(|_err| JsonResponse::<models::Rating>::build().internal_server_error(""))
+}
diff --git a/src/startup.rs b/src/startup.rs
index 0fb7d54d..b798ea88 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -81,6 +81,7 @@ pub async fn run(
                     .service(
                         web::scope("/rating")
                             .service(routes::rating::admin_get_handler)
+                            .service(routes::rating::admin_list_handler)
                     )
                     .service(
                         web::scope("/project")

From 9b130dd62c82ed758181dd9a12dcc14c7be36efb Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Fri, 12 Apr 2024 22:03:51 +0300
Subject: [PATCH 269/284] 47 user delete rating. partially

---
 ...412141011_casbin_user_rating_edit.down.sql |  3 +++
 ...40412141011_casbin_user_rating_edit.up.sql |  4 +++
 src/routes/rating/delete.rs                   | 25 +++++++++++++++++++
 src/routes/rating/mod.rs                      |  2 ++
 src/startup.rs                                |  3 ++-
 5 files changed, 36 insertions(+), 1 deletion(-)
 create mode 100644 src/routes/rating/delete.rs

diff --git a/migrations/20240412141011_casbin_user_rating_edit.down.sql b/migrations/20240412141011_casbin_user_rating_edit.down.sql
index a9a66790..32e4c56c 100644
--- a/migrations/20240412141011_casbin_user_rating_edit.down.sql
+++ b/migrations/20240412141011_casbin_user_rating_edit.down.sql
@@ -2,6 +2,9 @@
 DELETE FROM casbin_rule
 WHERE ptype = 'p' and v0 = 'group_user' and v1 = '/rating/:id' and v2 = 'PUT';
 
+DELETE FROM casbin_rule
+WHERE ptype = 'p' and v0 = 'group_user' and v1 = '/rating/:id' and v2 = 'DELETE';
+
 DELETE FROM casbin_rule
 WHERE ptype = 'p' and v0 = 'group_admin' and v1 = '/admin/rating/:id' and v2 = 'GET';
 
diff --git a/migrations/20240412141011_casbin_user_rating_edit.up.sql b/migrations/20240412141011_casbin_user_rating_edit.up.sql
index ca44313d..51f85fa2 100644
--- a/migrations/20240412141011_casbin_user_rating_edit.up.sql
+++ b/migrations/20240412141011_casbin_user_rating_edit.up.sql
@@ -5,6 +5,10 @@ INSERT INTO casbin_rule
 (id, ptype, v0, v1, v2, v3, v4, v5)
 VALUES((select max(id) + 1 from casbin_rule cr), 'p', 'group_user', '/rating/:id', 'PUT', '', '', '');
 
+INSERT INTO casbin_rule
+(id, ptype, v0, v1, v2, v3, v4, v5)
+VALUES((select max(id) + 1 from casbin_rule cr), 'p', 'group_user', '/rating/:id', 'DELETE', '', '', '');
+
 INSERT INTO casbin_rule
 (id, ptype, v0, v1, v2, v3, v4, v5)
 VALUES((select max(id) + 1 from casbin_rule cr), 'p', 'group_admin', '/admin/rating/:id', 'GET', '', '', '');
diff --git a/src/routes/rating/delete.rs b/src/routes/rating/delete.rs
new file mode 100644
index 00000000..646ea1de
--- /dev/null
+++ b/src/routes/rating/delete.rs
@@ -0,0 +1,25 @@
+use crate::db;
+use crate::helpers::JsonResponse;
+use crate::models;
+use actix_web::{delete, web, Responder, Result};
+use sqlx::PgPool;
+
+#[tracing::instrument(name = "User delete rating.")]
+#[delete("/{id}")]
+pub async fn user_delete_handler(
+    path: web::Path<(i32,)>,
+    pg_pool: web::Data<PgPool>,
+) -> Result<impl Responder> {
+    let rate_id = path.0;
+    let rating = db::rating::fetch(pg_pool.get_ref(), rate_id)
+        .await
+        .map_err(|_err| JsonResponse::<models::Rating>::build().internal_server_error(""))
+        .and_then(|rating| {
+            match rating {
+                Some(rating) if rating.hidden == Some(false) => { Ok(rating) },
+                _ => Err(JsonResponse::<models::Rating>::build().not_found("not found"))
+            }
+        })?;
+
+    Ok(JsonResponse::build().set_item(rating).ok("OK"))
+}
diff --git a/src/routes/rating/mod.rs b/src/routes/rating/mod.rs
index c654b782..2bd48dbb 100644
--- a/src/routes/rating/mod.rs
+++ b/src/routes/rating/mod.rs
@@ -1,7 +1,9 @@
 pub mod add;
 pub mod get;
 mod edit;
+mod delete;
 
 pub use add::*;
 pub use get::*;
 pub use edit::*;
+pub use delete::*;
diff --git a/src/startup.rs b/src/startup.rs
index b798ea88..e070bea3 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -60,9 +60,10 @@ pub async fn run(
             )
             .service(
                 web::scope("/rating")
-                    .service(routes::rating::user_add_handler)
                     .service(routes::rating::anonymous_get_handler)
                     .service(routes::rating::anonymous_list_handler)
+                    .service(routes::rating::user_add_handler)
+                    .service(routes::rating::user_delete_handler)
                     .service(routes::rating::user_edit_handler),
             )
             .service(

From 72ce432c711d75f05ced0c4d6219b46d7c23d11b Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 13 Apr 2024 15:16:43 +0300
Subject: [PATCH 270/284] 47 user delete rating

---
 src/db/rating.rs            |  4 +++-
 src/routes/rating/delete.rs | 18 +++++++++++++++---
 src/routes/rating/edit.rs   |  2 +-
 3 files changed, 19 insertions(+), 5 deletions(-)

diff --git a/src/db/rating.rs b/src/db/rating.rs
index e56a7473..32f19481 100644
--- a/src/db/rating.rs
+++ b/src/db/rating.rs
@@ -142,11 +142,13 @@ pub async fn update(pool: &PgPool, rating: models::Rating) -> Result<models::Rat
         SET 
             comment=$1,
             rate=$2,
+            hidden=$3,
             updated_at=NOW() at time zone 'utc'
-        WHERE id = $3
+        WHERE id = $4
         "#,
         rating.comment,
         rating.rate,
+        rating.hidden,
         rating.id
     )
     .execute(pool)
diff --git a/src/routes/rating/delete.rs b/src/routes/rating/delete.rs
index 646ea1de..a2d2846c 100644
--- a/src/routes/rating/delete.rs
+++ b/src/routes/rating/delete.rs
@@ -3,23 +3,35 @@ use crate::helpers::JsonResponse;
 use crate::models;
 use actix_web::{delete, web, Responder, Result};
 use sqlx::PgPool;
+use std::sync::Arc;
 
 #[tracing::instrument(name = "User delete rating.")]
 #[delete("/{id}")]
 pub async fn user_delete_handler(
+    user: web::ReqData<Arc<models::User>>,
     path: web::Path<(i32,)>,
     pg_pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
     let rate_id = path.0;
-    let rating = db::rating::fetch(pg_pool.get_ref(), rate_id)
+    let mut rating = db::rating::fetch(pg_pool.get_ref(), rate_id)
         .await
         .map_err(|_err| JsonResponse::<models::Rating>::build().internal_server_error(""))
         .and_then(|rating| {
             match rating {
-                Some(rating) if rating.hidden == Some(false) => { Ok(rating) },
+                Some(rating) if rating.user_id == user.id && rating.hidden == Some(false) =>  Ok(rating),
                 _ => Err(JsonResponse::<models::Rating>::build().not_found("not found"))
             }
         })?;
 
-    Ok(JsonResponse::build().set_item(rating).ok("OK"))
+    rating.hidden.insert(true);
+
+    db::rating::update(pg_pool.get_ref(), rating)
+        .await
+        .map(|rating| {
+            JsonResponse::<models::Rating>::build().ok("success")
+        })
+        .map_err(|err| {
+            tracing::error!("Failed to execute query: {:?}", err);
+            JsonResponse::<models::Rating>::build().internal_server_error("Rating not update")
+        })
 }
diff --git a/src/routes/rating/edit.rs b/src/routes/rating/edit.rs
index 06a65a84..9f06fcdc 100644
--- a/src/routes/rating/edit.rs
+++ b/src/routes/rating/edit.rs
@@ -30,7 +30,7 @@ pub async fn user_edit_handler(
         .map_err(|_err| JsonResponse::<models::Rating>::build().internal_server_error(""))
         .and_then(|rating| {
             match rating {
-                Some(rating) if rating.user_id == user.id=>  Ok(rating),
+                Some(rating) if rating.user_id == user.id && rating.hidden == Some(false) =>  Ok(rating),
                 _ => Err(JsonResponse::<models::Rating>::build().not_found("not found"))
             }
         })?;

From e2c07b8660a303784ac9d15bb96ade9d4ab401a5 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 13 Apr 2024 15:46:53 +0300
Subject: [PATCH 271/284] 47 rating admin edit handler

---
 ...412141011_casbin_user_rating_edit.down.sql |  3 ++
 ...40412141011_casbin_user_rating_edit.up.sql |  4 ++
 src/forms/rating/adminedit.rs                 | 30 +++++++++++++++
 src/forms/rating/mod.rs                       |  2 +
 src/routes/rating/edit.rs                     | 37 +++++++++++++++++++
 src/startup.rs                                |  1 +
 6 files changed, 77 insertions(+)
 create mode 100644 src/forms/rating/adminedit.rs

diff --git a/migrations/20240412141011_casbin_user_rating_edit.down.sql b/migrations/20240412141011_casbin_user_rating_edit.down.sql
index 32e4c56c..fa13ca87 100644
--- a/migrations/20240412141011_casbin_user_rating_edit.down.sql
+++ b/migrations/20240412141011_casbin_user_rating_edit.down.sql
@@ -2,6 +2,9 @@
 DELETE FROM casbin_rule
 WHERE ptype = 'p' and v0 = 'group_user' and v1 = '/rating/:id' and v2 = 'PUT';
 
+DELETE FROM casbin_rule
+WHERE ptype = 'p' and v0 = 'group_admin' and v1 = '/admin/rating/:id' and v2 = 'PUT';
+
 DELETE FROM casbin_rule
 WHERE ptype = 'p' and v0 = 'group_user' and v1 = '/rating/:id' and v2 = 'DELETE';
 
diff --git a/migrations/20240412141011_casbin_user_rating_edit.up.sql b/migrations/20240412141011_casbin_user_rating_edit.up.sql
index 51f85fa2..a138d327 100644
--- a/migrations/20240412141011_casbin_user_rating_edit.up.sql
+++ b/migrations/20240412141011_casbin_user_rating_edit.up.sql
@@ -5,6 +5,10 @@ INSERT INTO casbin_rule
 (id, ptype, v0, v1, v2, v3, v4, v5)
 VALUES((select max(id) + 1 from casbin_rule cr), 'p', 'group_user', '/rating/:id', 'PUT', '', '', '');
 
+INSERT INTO casbin_rule
+(id, ptype, v0, v1, v2, v3, v4, v5)
+VALUES((select max(id) + 1 from casbin_rule cr), 'p', 'group_admin', '/admin/rating/:id', 'PUT', '', '', '');
+
 INSERT INTO casbin_rule
 (id, ptype, v0, v1, v2, v3, v4, v5)
 VALUES((select max(id) + 1 from casbin_rule cr), 'p', 'group_user', '/rating/:id', 'DELETE', '', '', '');
diff --git a/src/forms/rating/adminedit.rs b/src/forms/rating/adminedit.rs
new file mode 100644
index 00000000..bf6baea8
--- /dev/null
+++ b/src/forms/rating/adminedit.rs
@@ -0,0 +1,30 @@
+use crate::models;
+use serde::{Deserialize, Serialize};
+use serde_valid::Validate;
+
+#[derive(Serialize, Deserialize, Debug, Validate)]
+pub struct AdminEditRating {
+    #[validate(max_length = 1000)]
+    pub comment: Option<String>, // always linked to a product
+    #[validate(minimum = 0)]
+    #[validate(maximum = 10)]
+    pub rate: Option<i32>, 
+    pub hidden: Option<bool>, 
+}
+
+impl AdminEditRating {
+    pub fn update(self, rating: &mut models::Rating) 
+    {
+        if let Some(comment) = self.comment {
+            rating.comment.insert(comment);
+        }
+
+        if let Some(rate) = self.rate {
+            rating.rate.insert(rate);
+        }
+
+        if let Some(hidden) = self.hidden {
+            rating.hidden.insert(hidden);
+        }
+    }
+}
diff --git a/src/forms/rating/mod.rs b/src/forms/rating/mod.rs
index ae8dbdf4..af230abf 100644
--- a/src/forms/rating/mod.rs
+++ b/src/forms/rating/mod.rs
@@ -1,5 +1,7 @@
 mod add;
 mod useredit;
+mod adminedit;
 
 pub use add::AddRating as Add;
 pub use useredit::UserEditRating as UserEdit;
+pub use adminedit::AdminEditRating as AdminEdit;
diff --git a/src/routes/rating/edit.rs b/src/routes/rating/edit.rs
index 9f06fcdc..f1fd9a23 100644
--- a/src/routes/rating/edit.rs
+++ b/src/routes/rating/edit.rs
@@ -49,3 +49,40 @@ pub async fn user_edit_handler(
             JsonResponse::<models::Rating>::build().internal_server_error("Rating not update")
         })
 }
+
+#[tracing::instrument(name = "Admin edit rating.")]
+#[put("/{id}")]
+pub async fn admin_edit_handler(
+    path: web::Path<(i32,)>,
+    form: web::Json<forms::rating::AdminEdit>,
+    pg_pool: web::Data<PgPool>,
+) -> Result<impl Responder> {
+    if let Err(errors) = form.validate() {
+        return Err(JsonResponse::<models::Rating>::build().form_error(errors.to_string()));
+    }
+
+    let rate_id = path.0;
+    let mut rating = db::rating::fetch(pg_pool.get_ref(), rate_id)
+        .await
+        .map_err(|_err| JsonResponse::<models::Rating>::build().internal_server_error(""))
+        .and_then(|rating| {
+            match rating {
+                Some(rating) =>  Ok(rating),
+                _ => Err(JsonResponse::<models::Rating>::build().not_found("not found"))
+            }
+        })?;
+
+    form.into_inner().update(&mut rating);
+
+    db::rating::update(pg_pool.get_ref(), rating)
+        .await
+        .map(|rating| {
+            JsonResponse::<models::Rating>::build()
+                .set_item(rating)
+                .ok("success")
+        })
+        .map_err(|err| {
+            tracing::error!("Failed to execute query: {:?}", err);
+            JsonResponse::<models::Rating>::build().internal_server_error("Rating not update")
+        })
+}
diff --git a/src/startup.rs b/src/startup.rs
index e070bea3..f7e865d6 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -83,6 +83,7 @@ pub async fn run(
                         web::scope("/rating")
                             .service(routes::rating::admin_get_handler)
                             .service(routes::rating::admin_list_handler)
+                            .service(routes::rating::admin_edit_handler)
                     )
                     .service(
                         web::scope("/project")

From 5255b65102f756009f23c194e098df22d1792b01 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 13 Apr 2024 15:55:44 +0300
Subject: [PATCH 272/284] 47 delete rating by admin

---
 ...412141011_casbin_user_rating_edit.down.sql |  3 ++
 ...40412141011_casbin_user_rating_edit.up.sql |  4 +++
 src/db/rating.rs                              | 22 ++++++++++++++
 src/routes/rating/delete.rs                   | 29 +++++++++++++++++++
 src/startup.rs                                |  1 +
 5 files changed, 59 insertions(+)

diff --git a/migrations/20240412141011_casbin_user_rating_edit.down.sql b/migrations/20240412141011_casbin_user_rating_edit.down.sql
index fa13ca87..41c5e57a 100644
--- a/migrations/20240412141011_casbin_user_rating_edit.down.sql
+++ b/migrations/20240412141011_casbin_user_rating_edit.down.sql
@@ -8,6 +8,9 @@ WHERE ptype = 'p' and v0 = 'group_admin' and v1 = '/admin/rating/:id' and v2 = '
 DELETE FROM casbin_rule
 WHERE ptype = 'p' and v0 = 'group_user' and v1 = '/rating/:id' and v2 = 'DELETE';
 
+DELETE FROM casbin_rule
+WHERE ptype = 'p' and v0 = 'group_admin' and v1 = '/admin/rating/:id' and v2 = 'DELETE';
+
 DELETE FROM casbin_rule
 WHERE ptype = 'p' and v0 = 'group_admin' and v1 = '/admin/rating/:id' and v2 = 'GET';
 
diff --git a/migrations/20240412141011_casbin_user_rating_edit.up.sql b/migrations/20240412141011_casbin_user_rating_edit.up.sql
index a138d327..527b64f6 100644
--- a/migrations/20240412141011_casbin_user_rating_edit.up.sql
+++ b/migrations/20240412141011_casbin_user_rating_edit.up.sql
@@ -13,6 +13,10 @@ INSERT INTO casbin_rule
 (id, ptype, v0, v1, v2, v3, v4, v5)
 VALUES((select max(id) + 1 from casbin_rule cr), 'p', 'group_user', '/rating/:id', 'DELETE', '', '', '');
 
+INSERT INTO casbin_rule
+(id, ptype, v0, v1, v2, v3, v4, v5)
+VALUES((select max(id) + 1 from casbin_rule cr), 'p', 'group_admin', '/admin/rating/:id', 'DELETE', '', '', '');
+
 INSERT INTO casbin_rule
 (id, ptype, v0, v1, v2, v3, v4, v5)
 VALUES((select max(id) + 1 from casbin_rule cr), 'p', 'group_admin', '/admin/rating/:id', 'GET', '', '', '');
diff --git a/src/db/rating.rs b/src/db/rating.rs
index 32f19481..2a3192e2 100644
--- a/src/db/rating.rs
+++ b/src/db/rating.rs
@@ -191,3 +191,25 @@ pub async fn fetch_all_visible(pool: &PgPool) -> Result<Vec<models::Rating>, Str
         "".to_string()
     })
 }
+
+pub async fn delete(pool: &PgPool, rating: models::Rating) -> Result<(), String> {
+    let query_span = tracing::info_span!("Deleting rating from the database");
+    sqlx::query!(
+        r#"
+        DELETE FROM rating
+        WHERE id = $1
+        "#,
+        rating.id
+    )
+    .execute(pool)
+    .instrument(query_span)
+    .await
+    .map(|_|{
+        tracing::info!("Rating {} has been deleted from the database", rating.id);
+        ()
+    })
+    .map_err(|err| {
+        tracing::error!("Failed to execute query: {:?}", err);
+        "".to_string()
+    })
+}
diff --git a/src/routes/rating/delete.rs b/src/routes/rating/delete.rs
index a2d2846c..e08ced94 100644
--- a/src/routes/rating/delete.rs
+++ b/src/routes/rating/delete.rs
@@ -35,3 +35,32 @@ pub async fn user_delete_handler(
             JsonResponse::<models::Rating>::build().internal_server_error("Rating not update")
         })
 }
+
+#[tracing::instrument(name = "Admin delete rating.")]
+#[delete("/{id}")]
+pub async fn admin_delete_handler(
+    user: web::ReqData<Arc<models::User>>,
+    path: web::Path<(i32,)>,
+    pg_pool: web::Data<PgPool>,
+) -> Result<impl Responder> {
+    let rate_id = path.0;
+    let mut rating = db::rating::fetch(pg_pool.get_ref(), rate_id)
+        .await
+        .map_err(|_err| JsonResponse::<models::Rating>::build().internal_server_error(""))
+        .and_then(|rating| {
+            match rating {
+                Some(rating) =>  Ok(rating),
+                _ => Err(JsonResponse::<models::Rating>::build().not_found("not found"))
+            }
+        })?;
+
+    db::rating::delete(pg_pool.get_ref(), rating)
+        .await
+        .map(|_| {
+            JsonResponse::<models::Rating>::build().ok("success")
+        })
+        .map_err(|err| {
+            tracing::error!("Failed to execute query: {:?}", err);
+            JsonResponse::<models::Rating>::build().internal_server_error("Rating not deleted")
+        })
+}
diff --git a/src/startup.rs b/src/startup.rs
index f7e865d6..b25154c3 100644
--- a/src/startup.rs
+++ b/src/startup.rs
@@ -84,6 +84,7 @@ pub async fn run(
                             .service(routes::rating::admin_get_handler)
                             .service(routes::rating::admin_list_handler)
                             .service(routes::rating::admin_edit_handler)
+                            .service(routes::rating::admin_delete_handler)
                     )
                     .service(
                         web::scope("/project")

From fa1b3f8e019fbad8fa408ecc7a1bb6fd45d7634e Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Wed, 17 Apr 2024 21:49:57 +0300
Subject: [PATCH 273/284] #54 dockerhub command

---
 src/console/commands/debug/casbin.rs    |  1 -
 src/console/commands/debug/dockerhub.rs | 21 +++++++++++++++++++++
 src/console/commands/debug/mod.rs       |  2 ++
 src/console/main.rs                     |  7 +++++++
 4 files changed, 30 insertions(+), 1 deletion(-)
 create mode 100644 src/console/commands/debug/dockerhub.rs

diff --git a/src/console/commands/debug/casbin.rs b/src/console/commands/debug/casbin.rs
index ba6bb623..afc685ea 100644
--- a/src/console/commands/debug/casbin.rs
+++ b/src/console/commands/debug/casbin.rs
@@ -1,7 +1,6 @@
 use crate::configuration::get_configuration;
 use actix_web::{rt, post, web, HttpResponse, Result, http::header::ContentType};
 use crate::middleware;
-//use actix_casbin_auth::casbin::CoreApi;
 use casbin::CoreApi;
 use sqlx::PgPool;
 
diff --git a/src/console/commands/debug/dockerhub.rs b/src/console/commands/debug/dockerhub.rs
new file mode 100644
index 00000000..a661e1c8
--- /dev/null
+++ b/src/console/commands/debug/dockerhub.rs
@@ -0,0 +1,21 @@
+use actix_web::{rt, Result};
+
+pub struct DockerhubCommand {
+    json: String,
+}
+
+impl DockerhubCommand {
+    pub fn new(json: String) -> Self {
+        Self { json }
+    }
+}
+
+impl crate::console::commands::CallableTrait for DockerhubCommand {
+    fn call(&self) -> Result<(), Box<dyn std::error::Error>> {
+        rt::System::new().block_on(async {
+            println!("{}", self.json);
+
+            Ok(())
+        })
+    }
+}
diff --git a/src/console/commands/debug/mod.rs b/src/console/commands/debug/mod.rs
index 48862746..0b5119d1 100644
--- a/src/console/commands/debug/mod.rs
+++ b/src/console/commands/debug/mod.rs
@@ -1,5 +1,7 @@
 mod json;
 mod casbin;
+mod dockerhub;
 
 pub use json::*;
 pub use casbin::*;
+pub use dockerhub::*;
diff --git a/src/console/main.rs b/src/console/main.rs
index f339b30c..0bdc1f45 100644
--- a/src/console/main.rs
+++ b/src/console/main.rs
@@ -48,6 +48,10 @@ enum DebugCommands {
         #[arg(long)]
         subject: String,
     },
+    Dockerhub {
+        #[arg(long)]
+        json: String,
+    }
 }
 
 #[derive(Debug, Subcommand)]
@@ -76,6 +80,9 @@ fn get_command(cli: Cli) -> Result<Box<dyn stacker::console::commands::CallableT
             DebugCommands::Casbin { action, path, subject } => Ok(Box::new(
                 stacker::console::commands::debug::CasbinCommand::new(action, path, subject),
             )),
+            DebugCommands::Dockerhub { json } => Ok(Box::new(
+                stacker::console::commands::debug::DockerhubCommand::new(json),
+            )),
         },
         Commands::MQ { command} => match command {
             AppMqCommands::Listen {} => Ok(Box::new(

From 8fa9b9b617fbb1f0e9bc5c1a2518882a0496df96 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Fri, 19 Apr 2024 21:25:19 +0300
Subject: [PATCH 274/284] 54 is_active. mut removed

---
 src/helpers/dockerhub.rs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/helpers/dockerhub.rs b/src/helpers/dockerhub.rs
index fda20f0f..025337ef 100644
--- a/src/helpers/dockerhub.rs
+++ b/src/helpers/dockerhub.rs
@@ -241,7 +241,7 @@ impl<'a> DockerHub<'a> {
                 }
             })
     }
-    pub async fn is_active(&'a mut self) -> Result<bool, String> {
+    pub async fn is_active(&'a self) -> Result<bool, String> {
         // if namespace/user is not set change endpoint and return a different response
 
         // let n = self.repos
@@ -362,4 +362,4 @@ impl<'a> TryFrom<&'a DockerImage> for DockerHub<'a> {
 
        Ok(hub)
     }
-}
\ No newline at end of file
+}

From 326619f6b428ef3ccc140a533561870f115692bd Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Fri, 19 Apr 2024 22:05:55 +0300
Subject: [PATCH 275/284] 54

---
 src/console/commands/debug/dockerhub.rs | 49 +++++++++++++++++++++++++
 1 file changed, 49 insertions(+)

diff --git a/src/console/commands/debug/dockerhub.rs b/src/console/commands/debug/dockerhub.rs
index a661e1c8..a425562c 100644
--- a/src/console/commands/debug/dockerhub.rs
+++ b/src/console/commands/debug/dockerhub.rs
@@ -1,4 +1,8 @@
 use actix_web::{rt, Result};
+use crate::helpers::dockerhub::DockerHub;
+use crate::forms::project::DockerImage;
+
+use tracing_subscriber::FmtSubscriber;
 
 pub struct DockerhubCommand {
     json: String,
@@ -12,8 +16,53 @@ impl DockerhubCommand {
 
 impl crate::console::commands::CallableTrait for DockerhubCommand {
     fn call(&self) -> Result<(), Box<dyn std::error::Error>> {
+        let subscriber = FmtSubscriber::builder()
+            .with_max_level(tracing::Level::DEBUG)
+            .finish();
+        tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
+
+
         rt::System::new().block_on(async {
             println!("{}", self.json);
+            let dockerImage: DockerImage = serde_json::from_str(&self.json)?;
+            let mut dockerhub = DockerHub::try_from(&dockerImage)?;
+            /*
+            if dockerhub.creds.username.is_empty() {
+            */
+            let lookupOfficialRepos = dockerhub.lookup_official_repos();
+            let lookupPublicRepos = dockerhub.lookup_public_repos();
+            let lookupPublicRepos1 = dockerhub.lookup_public_repos();
+            tokio::select! {
+                Ok(true) = lookupOfficialRepos => {println!("lookup oficial repos"); }
+                Ok(true) = lookupPublicRepos => {println!("lookup public repos"); }
+                Ok(true) = lookupPublicRepos1 => {println!("lookup public repos1"); }
+                //todo add timer
+                //complete => { println!("complete"); }
+                //default => { println!("no good result"); }
+                else => { println!("nothing found") }
+            }
+            /*
+                if Ok(true) == dockerhub.lookup_official_repos().await {
+                    println!("official: true");
+                    return Ok(());
+                } else {
+                    println!("official: false");
+                };
+
+                if Ok(true) == dockerhub.lookup_public_repos().await {
+                    println!("public: true");
+                    return Ok(());
+                };
+                */
+                /*
+            } else {
+                println!("username is not empty");
+                if Ok(true) == dockerhub.lookup_private_repo().await {
+                    tracing::debug!("private: true");
+                    return Ok(());
+                };
+            }
+            */
 
             Ok(())
         })

From 67b85388c0c3ce30808f1d8955abc8fd6a4a6b82 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 20 Apr 2024 07:23:16 +0300
Subject: [PATCH 276/284] 54 is_active with tokio::select!

---
 src/console/commands/debug/dockerhub.rs | 38 ++-----------------------
 src/helpers/dockerhub.rs                | 32 ++++++++++-----------
 2 files changed, 17 insertions(+), 53 deletions(-)

diff --git a/src/console/commands/debug/dockerhub.rs b/src/console/commands/debug/dockerhub.rs
index a425562c..7067ce90 100644
--- a/src/console/commands/debug/dockerhub.rs
+++ b/src/console/commands/debug/dockerhub.rs
@@ -26,43 +26,9 @@ impl crate::console::commands::CallableTrait for DockerhubCommand {
             println!("{}", self.json);
             let dockerImage: DockerImage = serde_json::from_str(&self.json)?;
             let mut dockerhub = DockerHub::try_from(&dockerImage)?;
-            /*
-            if dockerhub.creds.username.is_empty() {
-            */
-            let lookupOfficialRepos = dockerhub.lookup_official_repos();
-            let lookupPublicRepos = dockerhub.lookup_public_repos();
-            let lookupPublicRepos1 = dockerhub.lookup_public_repos();
-            tokio::select! {
-                Ok(true) = lookupOfficialRepos => {println!("lookup oficial repos"); }
-                Ok(true) = lookupPublicRepos => {println!("lookup public repos"); }
-                Ok(true) = lookupPublicRepos1 => {println!("lookup public repos1"); }
-                //todo add timer
-                //complete => { println!("complete"); }
-                //default => { println!("no good result"); }
-                else => { println!("nothing found") }
-            }
-            /*
-                if Ok(true) == dockerhub.lookup_official_repos().await {
-                    println!("official: true");
-                    return Ok(());
-                } else {
-                    println!("official: false");
-                };
+            let isActive = dockerhub.is_active().await?;
 
-                if Ok(true) == dockerhub.lookup_public_repos().await {
-                    println!("public: true");
-                    return Ok(());
-                };
-                */
-                /*
-            } else {
-                println!("username is not empty");
-                if Ok(true) == dockerhub.lookup_private_repo().await {
-                    tracing::debug!("private: true");
-                    return Ok(());
-                };
-            }
-            */
+            println!("image is active: {isActive}");
 
             Ok(())
         })
diff --git a/src/helpers/dockerhub.rs b/src/helpers/dockerhub.rs
index 025337ef..ce28831b 100644
--- a/src/helpers/dockerhub.rs
+++ b/src/helpers/dockerhub.rs
@@ -206,10 +206,15 @@ impl<'a> DockerHub<'a> {
     }
 
     pub async fn lookup_private_repo(&'a self) -> Result<bool, String> {
+        if self.creds.username.is_empty() {
+            return Ok(false);
+        }
+
         let url = format!(
             "https://hub.docker.com/v2/namespaces/{}/repositories/{}/tags",
             &self.creds.username, &self.repos
         );
+
         tracing::debug!("Validate image {:?}", url);
         let client = reqwest::Client::new()
             .get(url)
@@ -262,30 +267,23 @@ impl<'a> DockerHub<'a> {
         //     }
         // }
 
-        if self.creds.username.is_empty() {
-
-            if Ok(true) == self.lookup_official_repos().await {
-                tracing::debug!("official: true");
-                return Ok(true);
-            } else {
-                tracing::debug!("official: false");
-            };
+        tokio::select! {
+            Ok(true) = self.lookup_official_repos() => {
+                    tracing::debug!("official: true");
+                    return Ok(true);
+                }
 
-            if Ok(true) == self.lookup_public_repos().await {
+            Ok(true) = self.lookup_public_repos() => {
                 tracing::debug!("public: true");
                 return Ok(true);
-            };
-
-            Ok(false)
-
-        } else {
+            }
 
-            if Ok(true) == self.lookup_private_repo().await {
+            Ok(true) = self.lookup_private_repo() => {
                 tracing::debug!("private: true");
                 return Ok(true);
-            };
+            }
 
-            Ok(false)
+            else => { return Ok(false); }
         }
     }
 

From 8bb3a996a473e603233cae57ed175b207449d6b0 Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 20 Apr 2024 07:59:28 +0300
Subject: [PATCH 277/284] 54 login logic

---
 src/helpers/dockerhub.rs | 39 +++++++++++++++++++++------------------
 1 file changed, 21 insertions(+), 18 deletions(-)

diff --git a/src/helpers/dockerhub.rs b/src/helpers/dockerhub.rs
index ce28831b..2267dedc 100644
--- a/src/helpers/dockerhub.rs
+++ b/src/helpers/dockerhub.rs
@@ -6,7 +6,7 @@ use serde_valid::Validate;
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
 pub struct DockerHubToken {
-    pub token: Option<String>,
+    pub token: String,
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)]
@@ -104,9 +104,16 @@ pub struct DockerHub<'a> {
 }
 
 impl<'a> DockerHub<'a> {
-    pub async fn login(&'a self) -> Result<DockerHubToken, String> {
-        let endpoint = "https://hub.docker.com/v2/users/login";
+    pub async fn login(&'a self) -> Result<String, String> {
+        if self.creds.password.is_empty() {
+            return Err("Password is empty".to_string());
+        }
+
+        if self.creds.username.is_empty() {
+            return Err("Username is empty".to_string());
+        }
 
+        let endpoint = "https://hub.docker.com/v2/users/login";
         reqwest::Client::new()
             .post(endpoint)
             .json(&self.creds)
@@ -115,6 +122,7 @@ impl<'a> DockerHub<'a> {
             .map_err(|err| format!("{:?}", err))?
             .json::<DockerHubToken>()
             .await
+            .map(|dockerHubToken| dockerHubToken.token)
             .map_err(|err| format!("{:?}", err))
     }
 
@@ -125,7 +133,7 @@ impl<'a> DockerHub<'a> {
         let client = reqwest::Client::new()
             .get(&url)
             .header("Accept", "application/json");
-        let client = self.set_token(client).await?;
+        let client = self.set_token(client).await;
         client
             .send()
             .await
@@ -169,7 +177,7 @@ impl<'a> DockerHub<'a> {
         let client = reqwest::Client::new()
             .get(url)
             .header("Accept", "application/json");
-        let client = self.set_token(client).await?;
+        let client = self.set_token(client).await;
         client
             .send()
             .await
@@ -219,7 +227,7 @@ impl<'a> DockerHub<'a> {
         let client = reqwest::Client::new()
             .get(url)
             .header("Accept", "application/json");
-        let client = self.set_token(client).await?;
+        let client = self.set_token(client).await;
         client
             .send()
             .await
@@ -287,18 +295,13 @@ impl<'a> DockerHub<'a> {
         }
     }
 
-    pub async fn set_token(&'a self, client: RequestBuilder) -> Result<RequestBuilder, String> {
-        if self.creds.password.is_empty() {
-            tracing::debug!("Password is empty. Image should be public");
-            return Ok(client);
-        } else {
-        }
-        tracing::debug!("Password is set. Login..");
-        let token = self.login().await?;
-
-        match token.token {
-            None => Ok(client),
-            Some(token) => Ok(client.bearer_auth(token)),
+    pub async fn set_token(&'a self, client: RequestBuilder) -> RequestBuilder {
+        match self.login().await  {
+            Ok(token) => client.bearer_auth(token),
+            Err(msg) => {
+                tracing::debug!("set_token: {msg:?}");
+                client
+            }
         }
     }
 }

From bab76d0c20cb44866d14d44a691258cb4fe4032a Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Mon, 22 Apr 2024 17:54:51 +0300
Subject: [PATCH 278/284] 54 tracing

---
 src/helpers/dockerhub.rs | 58 ++++++++++++++--------------------------
 1 file changed, 20 insertions(+), 38 deletions(-)

diff --git a/src/helpers/dockerhub.rs b/src/helpers/dockerhub.rs
index 2267dedc..3108d5ce 100644
--- a/src/helpers/dockerhub.rs
+++ b/src/helpers/dockerhub.rs
@@ -104,6 +104,8 @@ pub struct DockerHub<'a> {
 }
 
 impl<'a> DockerHub<'a> {
+
+    #[tracing::instrument(name = "Dockerhub login.")]
     pub async fn login(&'a self) -> Result<String, String> {
         if self.creds.password.is_empty() {
             return Err("Password is empty".to_string());
@@ -113,9 +115,9 @@ impl<'a> DockerHub<'a> {
             return Err("Username is empty".to_string());
         }
 
-        let endpoint = "https://hub.docker.com/v2/users/login";
+        let url = "https://hub.docker.com/v2/users/login";
         reqwest::Client::new()
-            .post(endpoint)
+            .post(url)
             .json(&self.creds)
             .send()
             .await
@@ -126,16 +128,14 @@ impl<'a> DockerHub<'a> {
             .map_err(|err| format!("{:?}", err))
     }
 
+    #[tracing::instrument(name = "Lookup public repos")]
     pub async fn lookup_public_repos(&'a self) -> Result<bool, String> {
-
         let url = format!("https://hub.docker.com/v2/repositories/{}", self.repos);
-        tracing::debug!("Validate public repository {:?}", &url);
         let client = reqwest::Client::new()
             .get(&url)
             .header("Accept", "application/json");
-        let client = self.set_token(client).await;
-        client
-            .send()
+
+        client.send()
             .await
             .map_err(|err| {
                 let msg = format!("🟥Error response {:?}", err);
@@ -157,29 +157,23 @@ impl<'a> DockerHub<'a> {
                         .results
                         .into_iter()
                         .any(|repo| repo.status == Some(1));
-                    tracing::debug!("✅ Image is active. url: {:?}", &url);
+                    tracing::debug!("✅ Public mage is active. url: {:?}", &url);
                     active
                 } else {
-                    tracing::debug!("🟥 Image tag is not active, url: {:?}", &url);
+                    tracing::debug!("🟥 Public image tag is not active, url: {:?}", &url);
                     false
                 }
             })
     }
 
+    #[tracing::instrument(name = "Lookup official repos")]
     pub async fn lookup_official_repos(&'a self) -> Result<bool, String> {
-        // search in official library repositories
         let url = format!("https://hub.docker.com/v2/repositories/library/{}/tags", self.repos);
-        return self.lookup(&url).await;
-    }
-
-    pub async fn lookup(&'a self, url: &String) -> Result<bool, String> {
-        tracing::debug!("Search official repos {:?}", url);
         let client = reqwest::Client::new()
             .get(url)
             .header("Accept", "application/json");
-        let client = self.set_token(client).await;
-        client
-            .send()
+
+        client.send()
             .await
             .map_err(|err| format!("🟥{}", err))?
             .json::<OfficialRepoResults>()
@@ -198,37 +192,35 @@ impl<'a> DockerHub<'a> {
                         .any(|tag| {
                             tracing::debug!("official: {:?}", tag);
                             if "active".to_string() == tag.tag_status {
-                                tracing::debug!("✅ Image is active");
                                 true
                             } else {
                                 false
                             }
                         });
-                    tracing::debug!("✅ search official repos result is {:?}", result);
+                    tracing::debug!("✅ Official mage is active. url: {:?}", result);
                     result
                 } else {
-                    tracing::debug!("🟥 Image tag is not active");
+                    tracing::debug!("🟥 Official image tag is not active");
                     false
                 }
             })
     }
 
+    #[tracing::instrument(name = "Lookup private repos")]
     pub async fn lookup_private_repo(&'a self) -> Result<bool, String> {
-        if self.creds.username.is_empty() {
-            return Ok(false);
-        }
+        let token = self.login().await?;
 
         let url = format!(
             "https://hub.docker.com/v2/namespaces/{}/repositories/{}/tags",
             &self.creds.username, &self.repos
         );
 
-        tracing::debug!("Validate image {:?}", url);
+        tracing::debug!("Search private repos {:?}", url);
         let client = reqwest::Client::new()
             .get(url)
             .header("Accept", "application/json");
-        let client = self.set_token(client).await;
-        client
+
+        client.bearer_auth(token)
             .send()
             .await
             .map_err(|err| format!("🟥{}", err))?
@@ -246,7 +238,6 @@ impl<'a> DockerHub<'a> {
                         .results
                         .into_iter()
                         .any(|tag| tag.tag_status.contains("active"));
-                    tracing::debug!("✅ Image is active");
                     return active;
                 } else {
                     tracing::debug!("🟥 Image tag is not active");
@@ -254,6 +245,7 @@ impl<'a> DockerHub<'a> {
                 }
             })
     }
+
     pub async fn is_active(&'a self) -> Result<bool, String> {
         // if namespace/user is not set change endpoint and return a different response
 
@@ -294,16 +286,6 @@ impl<'a> DockerHub<'a> {
             else => { return Ok(false); }
         }
     }
-
-    pub async fn set_token(&'a self, client: RequestBuilder) -> RequestBuilder {
-        match self.login().await  {
-            Ok(token) => client.bearer_auth(token),
-            Err(msg) => {
-                tracing::debug!("set_token: {msg:?}");
-                client
-            }
-        }
-    }
 }
 
 

From 5992f0219469982648603280a19ea9fe22ab6eca Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Wed, 24 Apr 2024 21:42:03 +0300
Subject: [PATCH 279/284] 54 fixed test

---
 tests/dockerhub.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/dockerhub.rs b/tests/dockerhub.rs
index 25b30e25..79d46e67 100644
--- a/tests/dockerhub.rs
+++ b/tests/dockerhub.rs
@@ -120,7 +120,7 @@ async fn test_docker_named_volume() {
         container_path: Some("/var/www/flaskdata".to_owned()),
     };
 
-    let cv:ComposeVolume = volume.into();
+    let cv:ComposeVolume = (&volume).into();
     println!("ComposeVolume: {:?}", cv);
     println!("{:?}", cv.driver_opts);
     assert_eq!(Some("flask-data".to_string()), cv.name);

From a59e8a25dd13f40c8682e63928b894abddc831db Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Fri, 26 Apr 2024 17:57:34 +0300
Subject: [PATCH 280/284] 57 rating output in dependence of user's status

---
 src/lib.rs                    |  1 +
 src/routes/rating/add.rs      | 15 ++++++++-------
 src/routes/rating/delete.rs   | 17 +++++++++--------
 src/routes/rating/edit.rs     | 21 +++++++++++----------
 src/routes/rating/get.rs      | 15 +++++++++------
 src/views/mod.rs              |  1 +
 src/views/rating/admin.rs     | 33 +++++++++++++++++++++++++++++++++
 src/views/rating/anonymous.rs | 27 +++++++++++++++++++++++++++
 src/views/rating/mod.rs       |  7 +++++++
 src/views/rating/user.rs      | 31 +++++++++++++++++++++++++++++++
 10 files changed, 137 insertions(+), 31 deletions(-)
 create mode 100644 src/views/mod.rs
 create mode 100644 src/views/rating/admin.rs
 create mode 100644 src/views/rating/anonymous.rs
 create mode 100644 src/views/rating/mod.rs
 create mode 100644 src/views/rating/user.rs

diff --git a/src/lib.rs b/src/lib.rs
index 3c6af7cc..78852886 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -5,6 +5,7 @@ pub mod forms;
 pub mod helpers;
 mod middleware;
 pub mod models;
+pub mod views;
 pub mod routes;
 pub mod services;
 pub mod startup;
diff --git a/src/routes/rating/add.rs b/src/routes/rating/add.rs
index ff7de887..a97b8d9e 100644
--- a/src/routes/rating/add.rs
+++ b/src/routes/rating/add.rs
@@ -1,4 +1,5 @@
 use crate::forms;
+use crate::views;
 use crate::helpers::JsonResponse;
 use crate::models;
 use crate::db;
@@ -20,22 +21,22 @@ pub async fn user_add_handler(
     pg_pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
     if let Err(errors) = form.validate() {
-        return Err(JsonResponse::<models::Rating>::build().form_error(errors.to_string()));
+        return Err(JsonResponse::<views::rating::User>::build().form_error(errors.to_string()));
     }
 
     let _product = db::product::fetch_by_obj(pg_pool.get_ref(), form.obj_id)
         .await
-        .map_err(|_msg| JsonResponse::<models::Rating>::build().internal_server_error(_msg))?
-        .ok_or_else(|| JsonResponse::<models::Rating>::build().not_found("not found"))?
+        .map_err(|_msg| JsonResponse::<views::rating::User>::build().internal_server_error(_msg))?
+        .ok_or_else(|| JsonResponse::<views::rating::User>::build().not_found("not found"))?
         ; 
 
     let rating = db::rating::fetch_by_obj_and_user_and_category(
         pg_pool.get_ref(), form.obj_id, user.id.clone(), form.category)
         .await
-        .map_err(|err| JsonResponse::<models::Rating>::build().internal_server_error(err))?;
+        .map_err(|err| JsonResponse::<views::rating::User>::build().internal_server_error(err))?;
 
     if rating.is_some() {
-        return Err(JsonResponse::<models::Rating>::build().bad_request("already rated"));
+        return Err(JsonResponse::<views::rating::User>::build().bad_request("already rated"));
     }
 
     let mut rating: models::Rating = form.into_inner().into();
@@ -43,7 +44,7 @@ pub async fn user_add_handler(
 
     db::rating::insert(pg_pool.get_ref(), rating)
         .await
-        .map(|rating| JsonResponse::build().set_item(rating).ok("success"))
-        .map_err(|_err| JsonResponse::<models::Rating>::build()
+        .map(|rating| JsonResponse::build().set_item(Into::<views::rating::User>::into(rating)).ok("success"))
+        .map_err(|_err| JsonResponse::<views::rating::User>::build()
             .internal_server_error("Failed to insert"))
 }
diff --git a/src/routes/rating/delete.rs b/src/routes/rating/delete.rs
index e08ced94..2dde9c72 100644
--- a/src/routes/rating/delete.rs
+++ b/src/routes/rating/delete.rs
@@ -1,6 +1,7 @@
 use crate::db;
 use crate::helpers::JsonResponse;
 use crate::models;
+use crate::views;
 use actix_web::{delete, web, Responder, Result};
 use sqlx::PgPool;
 use std::sync::Arc;
@@ -15,11 +16,11 @@ pub async fn user_delete_handler(
     let rate_id = path.0;
     let mut rating = db::rating::fetch(pg_pool.get_ref(), rate_id)
         .await
-        .map_err(|_err| JsonResponse::<models::Rating>::build().internal_server_error(""))
+        .map_err(|_err| JsonResponse::<views::rating::User>::build().internal_server_error(""))
         .and_then(|rating| {
             match rating {
                 Some(rating) if rating.user_id == user.id && rating.hidden == Some(false) =>  Ok(rating),
-                _ => Err(JsonResponse::<models::Rating>::build().not_found("not found"))
+                _ => Err(JsonResponse::<views::rating::User>::build().not_found("not found"))
             }
         })?;
 
@@ -28,11 +29,11 @@ pub async fn user_delete_handler(
     db::rating::update(pg_pool.get_ref(), rating)
         .await
         .map(|rating| {
-            JsonResponse::<models::Rating>::build().ok("success")
+            JsonResponse::<views::rating::User>::build().ok("success")
         })
         .map_err(|err| {
             tracing::error!("Failed to execute query: {:?}", err);
-            JsonResponse::<models::Rating>::build().internal_server_error("Rating not update")
+            JsonResponse::<views::rating::User>::build().internal_server_error("Rating not update")
         })
 }
 
@@ -46,21 +47,21 @@ pub async fn admin_delete_handler(
     let rate_id = path.0;
     let mut rating = db::rating::fetch(pg_pool.get_ref(), rate_id)
         .await
-        .map_err(|_err| JsonResponse::<models::Rating>::build().internal_server_error(""))
+        .map_err(|_err| JsonResponse::<views::rating::Admin>::build().internal_server_error(""))
         .and_then(|rating| {
             match rating {
                 Some(rating) =>  Ok(rating),
-                _ => Err(JsonResponse::<models::Rating>::build().not_found("not found"))
+                _ => Err(JsonResponse::<views::rating::Admin>::build().not_found("not found"))
             }
         })?;
 
     db::rating::delete(pg_pool.get_ref(), rating)
         .await
         .map(|_| {
-            JsonResponse::<models::Rating>::build().ok("success")
+            JsonResponse::<views::rating::Admin>::build().ok("success")
         })
         .map_err(|err| {
             tracing::error!("Failed to execute query: {:?}", err);
-            JsonResponse::<models::Rating>::build().internal_server_error("Rating not deleted")
+            JsonResponse::<views::rating::Admin>::build().internal_server_error("Rating not deleted")
         })
 }
diff --git a/src/routes/rating/edit.rs b/src/routes/rating/edit.rs
index f1fd9a23..260c450c 100644
--- a/src/routes/rating/edit.rs
+++ b/src/routes/rating/edit.rs
@@ -1,6 +1,7 @@
 use crate::forms;
 use crate::helpers::JsonResponse;
 use crate::models;
+use crate::views;
 use crate::db;
 use actix_web::{put, web, Responder, Result};
 use sqlx::PgPool;
@@ -27,11 +28,11 @@ pub async fn user_edit_handler(
     let rate_id = path.0;
     let mut rating = db::rating::fetch(pg_pool.get_ref(), rate_id)
         .await
-        .map_err(|_err| JsonResponse::<models::Rating>::build().internal_server_error(""))
+        .map_err(|_err| JsonResponse::<views::rating::User>::build().internal_server_error(""))
         .and_then(|rating| {
             match rating {
                 Some(rating) if rating.user_id == user.id && rating.hidden == Some(false) =>  Ok(rating),
-                _ => Err(JsonResponse::<models::Rating>::build().not_found("not found"))
+                _ => Err(JsonResponse::<views::rating::User>::build().not_found("not found"))
             }
         })?;
 
@@ -40,13 +41,13 @@ pub async fn user_edit_handler(
     db::rating::update(pg_pool.get_ref(), rating)
         .await
         .map(|rating| {
-            JsonResponse::<models::Rating>::build()
-                .set_item(rating)
+            JsonResponse::build()
+                .set_item(Into::<views::rating::User>::into(rating))
                 .ok("success")
         })
         .map_err(|err| {
             tracing::error!("Failed to execute query: {:?}", err);
-            JsonResponse::<models::Rating>::build().internal_server_error("Rating not update")
+            JsonResponse::<views::rating::User>::build().internal_server_error("Rating not update")
         })
 }
 
@@ -64,11 +65,11 @@ pub async fn admin_edit_handler(
     let rate_id = path.0;
     let mut rating = db::rating::fetch(pg_pool.get_ref(), rate_id)
         .await
-        .map_err(|_err| JsonResponse::<models::Rating>::build().internal_server_error(""))
+        .map_err(|_err| JsonResponse::<views::rating::Admin>::build().internal_server_error(""))
         .and_then(|rating| {
             match rating {
                 Some(rating) =>  Ok(rating),
-                _ => Err(JsonResponse::<models::Rating>::build().not_found("not found"))
+                _ => Err(JsonResponse::<views::rating::Admin>::build().not_found("not found"))
             }
         })?;
 
@@ -77,12 +78,12 @@ pub async fn admin_edit_handler(
     db::rating::update(pg_pool.get_ref(), rating)
         .await
         .map(|rating| {
-            JsonResponse::<models::Rating>::build()
-                .set_item(rating)
+            JsonResponse::<views::rating::Admin>::build()
+                .set_item(Into::<views::rating::Admin>::into(rating))
                 .ok("success")
         })
         .map_err(|err| {
             tracing::error!("Failed to execute query: {:?}", err);
-            JsonResponse::<models::Rating>::build().internal_server_error("Rating not update")
+            JsonResponse::<views::rating::Admin>::build().internal_server_error("Rating not update")
         })
 }
diff --git a/src/routes/rating/get.rs b/src/routes/rating/get.rs
index 1880b7be..752f0dfc 100644
--- a/src/routes/rating/get.rs
+++ b/src/routes/rating/get.rs
@@ -1,8 +1,10 @@
 use crate::db;
 use crate::helpers::JsonResponse;
 use crate::models;
+use crate::views;
 use actix_web::{get, web, Responder, Result};
 use sqlx::PgPool;
+use std::convert::Into;
 
 #[tracing::instrument(name = "Anonymouse get rating.")]
 #[get("/{id}")]
@@ -13,15 +15,15 @@ pub async fn anonymous_get_handler(
     let rate_id = path.0;
     let rating = db::rating::fetch(pg_pool.get_ref(), rate_id)
         .await
-        .map_err(|_err| JsonResponse::<models::Rating>::build().internal_server_error(""))
+        .map_err(|_err| JsonResponse::<views::rating::Anonymous>::build().internal_server_error(""))
         .and_then(|rating| {
             match rating {
                 Some(rating) if rating.hidden == Some(false) => { Ok(rating) },
-                _ => Err(JsonResponse::<models::Rating>::build().not_found("not found"))
+                _ => Err(JsonResponse::<views::rating::Anonymous>::build().not_found("not found"))
             }
         })?;
 
-    Ok(JsonResponse::build().set_item(rating).ok("OK"))
+    Ok(JsonResponse::build().set_item(Into::<views::rating::Anonymous>::into(rating)).ok("OK"))
 }
 
 #[tracing::instrument(name = "Anonymous get all ratings.")]
@@ -30,6 +32,7 @@ pub async fn anonymous_list_handler(
     path: web::Path<()>,
     pg_pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
+    //todo
     db::rating::fetch_all_visible(pg_pool.get_ref())
         .await
         .map(|ratings| JsonResponse::build().set_list(ratings).ok("OK"))
@@ -45,15 +48,15 @@ pub async fn admin_get_handler(
     let rate_id = path.0;
     let rating = db::rating::fetch(pg_pool.get_ref(), rate_id)
         .await
-        .map_err(|_err| JsonResponse::<models::Rating>::build().internal_server_error(""))
+        .map_err(|_err| JsonResponse::<views::rating::Admin>::build().internal_server_error(""))
         .and_then(|rating| {
             match rating {
                 Some(rating) => { Ok(rating) },
-                _ => Err(JsonResponse::<models::Rating>::build().not_found("not found"))
+                _ => Err(JsonResponse::<views::rating::Admin>::build().not_found("not found"))
             }
         })?;
 
-    Ok(JsonResponse::build().set_item(rating).ok("OK"))
+    Ok(JsonResponse::build().set_item(Into::<views::rating::Admin>::into(rating)).ok("OK"))
 }
 
 #[tracing::instrument(name = "Admin get the list of ratings.")]
diff --git a/src/views/mod.rs b/src/views/mod.rs
new file mode 100644
index 00000000..1795238b
--- /dev/null
+++ b/src/views/mod.rs
@@ -0,0 +1 @@
+pub mod rating;
diff --git a/src/views/rating/admin.rs b/src/views/rating/admin.rs
new file mode 100644
index 00000000..0991dc6b
--- /dev/null
+++ b/src/views/rating/admin.rs
@@ -0,0 +1,33 @@
+use crate::models;
+use std::convert::From;
+use chrono::{DateTime, Utc};
+use serde::{Serialize};
+
+#[derive(Debug, Serialize, Default)]
+pub struct Admin {
+    pub id: i32,
+    pub user_id: String, // external user_id, 100, taken using token (middleware?)
+    pub obj_id: i32,     // id of the external object
+    pub category: models::RateCategory, // rating of product | rating of service etc
+    pub comment: Option<String>, // always linked to a product
+    pub hidden: Option<bool>, // rating can be hidden for non-adequate user behaviour
+    pub rate: Option<i32>,
+    pub created_at: DateTime<Utc>,
+    pub updated_at: DateTime<Utc>,
+}
+
+impl From<models::Rating> for Admin {
+    fn from(rating: models::Rating) -> Self {
+        Self {
+            id: rating.id,
+            user_id: rating.user_id,
+            obj_id: rating.obj_id,
+            category: rating.category,
+            comment: rating.comment,
+            hidden: rating.hidden,
+            rate: rating.rate,
+            created_at: rating.created_at,
+            updated_at: rating.updated_at
+        }
+    }
+}
diff --git a/src/views/rating/anonymous.rs b/src/views/rating/anonymous.rs
new file mode 100644
index 00000000..2871ab4c
--- /dev/null
+++ b/src/views/rating/anonymous.rs
@@ -0,0 +1,27 @@
+use crate::models;
+use std::convert::From;
+use chrono::{DateTime, Utc};
+use serde::{Serialize};
+
+#[derive(Debug, Serialize, Default)]
+pub struct Anonymous {
+    pub id: i32,
+    pub user_id: String, // external user_id, 100, taken using token (middleware?)
+    pub obj_id: i32,     // id of the external object
+    pub category: models::RateCategory, // rating of product | rating of service etc
+    pub comment: Option<String>, // always linked to a product
+    pub rate: Option<i32>,
+}
+
+impl From<models::Rating> for Anonymous {
+    fn from(rating: models::Rating) -> Self {
+        Self {
+            id: rating.id,
+            user_id: rating.user_id,
+            obj_id: rating.obj_id,
+            category: rating.category,
+            comment: rating.comment,
+            rate: rating.rate,
+        }
+    }
+}
diff --git a/src/views/rating/mod.rs b/src/views/rating/mod.rs
new file mode 100644
index 00000000..6474d91a
--- /dev/null
+++ b/src/views/rating/mod.rs
@@ -0,0 +1,7 @@
+mod anonymous;
+mod user;
+mod admin;
+
+pub use anonymous::Anonymous as Anonymous;
+pub use user::User as User;
+pub use admin::Admin as Admin;
diff --git a/src/views/rating/user.rs b/src/views/rating/user.rs
new file mode 100644
index 00000000..901f6e5d
--- /dev/null
+++ b/src/views/rating/user.rs
@@ -0,0 +1,31 @@
+use crate::models;
+use std::convert::From;
+use chrono::{DateTime, Utc};
+use serde::{Serialize};
+
+#[derive(Debug, Serialize, Default)]
+pub struct User {
+    pub id: i32,
+    pub user_id: String, // external user_id, 100, taken using token (middleware?)
+    pub obj_id: i32,     // id of the external object
+    pub category: models::RateCategory, // rating of product | rating of service etc
+    pub comment: Option<String>, // always linked to a product
+    pub rate: Option<i32>,
+    pub created_at: DateTime<Utc>,
+    pub updated_at: DateTime<Utc>,
+}
+
+impl From<models::Rating> for User {
+    fn from(rating: models::Rating) -> Self {
+        Self {
+            id: rating.id,
+            user_id: rating.user_id,
+            obj_id: rating.obj_id,
+            category: rating.category,
+            comment: rating.comment,
+            rate: rating.rate,
+            created_at: rating.created_at,
+            updated_at: rating.updated_at
+        }
+    }
+}

From d19c9e998eae130b3eb3eb2574f0cc5bdbaab7ec Mon Sep 17 00:00:00 2001
From: petru <smart__petea@mail.ru>
Date: Sat, 27 Apr 2024 07:12:42 +0300
Subject: [PATCH 281/284] 57 model Rating. fully removed serialize

---
 Cargo.lock                |  4 ++--
 Cargo.toml                |  3 +--
 src/models/rating.rs      |  2 +-
 src/routes/rating/edit.rs |  4 ++--
 src/routes/rating/get.rs  | 25 ++++++++++++++++++++-----
 5 files changed, 26 insertions(+), 12 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 2e0a4366..14fb9588 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4,8 +4,8 @@ version = 3
 
 [[package]]
 name = "actix-casbin-auth"
-version = "1.0.0"
-source = "git+https://github.com/smart--petea/actix-casbin-auth.git?branch=dirty-master#bbc6d08900521c684c46bab59c4b92394ca5bd57"
+version = "1.1.0"
+source = "git+https://github.com/casbin-rs/actix-casbin-auth.git#66662102a92fe1ae80ad427e07c1879cbdf65f4f"
 dependencies = [
  "actix-service",
  "actix-web",
diff --git a/Cargo.toml b/Cargo.toml
index 578a8098..d9b981c4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -57,8 +57,7 @@ brotli = "3.4.0"
 serde_path_to_error = "0.1.14"
 deadpool-lapin = "0.11.0"
 docker-compose-types = "0.7.0"
-#actix-casbin-auth = { git = "https://github.com/casbin-rs/actix-casbin-auth.git"}
-actix-casbin-auth = { git = "https://github.com/smart--petea/actix-casbin-auth.git", branch = "dirty-master"}
+actix-casbin-auth = { git = "https://github.com/casbin-rs/actix-casbin-auth.git"}
 casbin = "2.2.0"
 aes-gcm = "0.10.3"
 base64 = "0.22.0"
diff --git a/src/models/rating.rs b/src/models/rating.rs
index 55a4444b..d6f1eda7 100644
--- a/src/models/rating.rs
+++ b/src/models/rating.rs
@@ -2,7 +2,7 @@ use chrono::{DateTime, Utc};
 use serde::{Serialize};
 use crate::models;
 
-#[derive(Debug, Serialize, Default)]
+#[derive(Debug, Default)]
 pub struct Rating {
     pub id: i32,
     pub user_id: String, // external user_id, 100, taken using token (middleware?)
diff --git a/src/routes/rating/edit.rs b/src/routes/rating/edit.rs
index 260c450c..88cd7a3f 100644
--- a/src/routes/rating/edit.rs
+++ b/src/routes/rating/edit.rs
@@ -22,7 +22,7 @@ pub async fn user_edit_handler(
     pg_pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
     if let Err(errors) = form.validate() {
-        return Err(JsonResponse::<models::Rating>::build().form_error(errors.to_string()));
+        return Err(JsonResponse::<views::rating::User>::build().form_error(errors.to_string()));
     }
 
     let rate_id = path.0;
@@ -59,7 +59,7 @@ pub async fn admin_edit_handler(
     pg_pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
     if let Err(errors) = form.validate() {
-        return Err(JsonResponse::<models::Rating>::build().form_error(errors.to_string()));
+        return Err(JsonResponse::<views::rating::Admin>::build().form_error(errors.to_string()));
     }
 
     let rate_id = path.0;
diff --git a/src/routes/rating/get.rs b/src/routes/rating/get.rs
index 752f0dfc..08f7d9ce 100644
--- a/src/routes/rating/get.rs
+++ b/src/routes/rating/get.rs
@@ -32,11 +32,18 @@ pub async fn anonymous_list_handler(
     path: web::Path<()>,
     pg_pool: web::Data<PgPool>,
 ) -> Result<impl Responder> {
-    //todo
     db::rating::fetch_all_visible(pg_pool.get_ref())
         .await
-        .map(|ratings| JsonResponse::build().set_list(ratings).ok("OK"))
-        .map_err(|_err| JsonResponse::<models::Rating>::build().internal_server_error(""))
+        .map(|ratings| {
+            let ratings = ratings
+                .into_iter()
+                .map(Into::into)
+                .collect::<Vec<views::rating::Anonymous>>()
+                ;
+
+            JsonResponse::build().set_list(ratings).ok("OK")
+        })
+        .map_err(|_err| JsonResponse::<views::rating::Anonymous>::build().internal_server_error(""))
 }
 
 #[tracing::instrument(name = "Admin get rating.")]
@@ -67,6 +74,14 @@ pub async fn admin_list_handler(
 ) -> Result<impl Responder> {
     db::rating::fetch_all(pg_pool.get_ref())
         .await
-        .map(|ratings| JsonResponse::build().set_list(ratings).ok("OK"))
-        .map_err(|_err| JsonResponse::<models::Rating>::build().internal_server_error(""))
+        .map(|ratings| {
+            let ratings = ratings
+                .into_iter()
+                .map(Into::into)
+                .collect::<Vec<views::rating::Admin>>()
+                ;
+
+            JsonResponse::build().set_list(ratings).ok("OK")
+        })
+        .map_err(|_err| JsonResponse::<views::rating::Admin>::build().internal_server_error(""))
 }

From 2fc2214ebb980f262718154b81a29f9cc71dcedc Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Sun, 28 Apr 2024 11:02:05 +0300
Subject: [PATCH 282/284] check public repo fix

---
 src/forms/project/docker_image.rs | 13 ++++++++++---
 src/helpers/dockerhub.rs          | 10 ++++++++--
 tests/dockerhub.rs                | 22 ++++++++++++++++++----
 3 files changed, 36 insertions(+), 9 deletions(-)

diff --git a/src/forms/project/docker_image.rs b/src/forms/project/docker_image.rs
index a018174e..acfa3d0f 100644
--- a/src/forms/project/docker_image.rs
+++ b/src/forms/project/docker_image.rs
@@ -23,17 +23,24 @@ pub struct DockerImage {
 }
 
 impl fmt::Display for DockerImage {
+    // dh_image = trydirect/postgres:latest
+    // dh_nmsp = trydirect, dh_repo_name=postgres
+    // dh_nmsp = trydirect dh_repo_name=postgres:v8
+    // namespace/repo_name/tag
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         let dh_image = self.dockerhub_image.as_ref().map(String::as_str).unwrap_or("");
+        println!("{:?}", &dh_image);
         let dh_nmspc = self.dockerhub_user.as_ref().map(String::as_str).unwrap_or("");
-        let dh_name = self.dockerhub_name.as_ref().map(String::as_str).unwrap_or("");
+        println!("{:?}", &dh_nmspc);
+        let dh_repo_name = self.dockerhub_name.as_ref().map(String::as_str).unwrap_or("");
+        println!("{:?}", &dh_repo_name);
 
         write!(
             f,
             "{}{}{}",
             if !dh_nmspc.is_empty() { format!("{}/", dh_nmspc) } else { String::new() },
-            if !dh_name.is_empty() { dh_name } else { dh_image },
-            if !dh_name.contains(":") && dh_image.is_empty() { ":latest".to_string() } else { String::new() },
+            if !dh_repo_name.is_empty() { dh_repo_name } else { dh_image },
+            if !dh_repo_name.contains(":") && dh_image.is_empty() { ":latest".to_string() } else { String::new() },
         )
     }
 }
diff --git a/src/helpers/dockerhub.rs b/src/helpers/dockerhub.rs
index 3108d5ce..64b88329 100644
--- a/src/helpers/dockerhub.rs
+++ b/src/helpers/dockerhub.rs
@@ -130,6 +130,9 @@ impl<'a> DockerHub<'a> {
 
     #[tracing::instrument(name = "Lookup public repos")]
     pub async fn lookup_public_repos(&'a self) -> Result<bool, String> {
+        if !self.creds.username.is_empty() {
+            return Ok(false);
+        }
         let url = format!("https://hub.docker.com/v2/repositories/{}", self.repos);
         let client = reqwest::Client::new()
             .get(&url)
@@ -157,7 +160,7 @@ impl<'a> DockerHub<'a> {
                         .results
                         .into_iter()
                         .any(|repo| repo.status == Some(1));
-                    tracing::debug!("✅ Public mage is active. url: {:?}", &url);
+                    tracing::debug!("✅ Public image is active. url: {:?}", &url);
                     active
                 } else {
                     tracing::debug!("🟥 Public image tag is not active, url: {:?}", &url);
@@ -270,16 +273,19 @@ impl<'a> DockerHub<'a> {
         tokio::select! {
             Ok(true) = self.lookup_official_repos() => {
                     tracing::debug!("official: true");
+                    println!("official: true");
                     return Ok(true);
                 }
 
-            Ok(true) = self.lookup_public_repos() => {
+            Ok(true) = self.lookup_public_repos()  => {
                 tracing::debug!("public: true");
+                println!("public: true");
                 return Ok(true);
             }
 
             Ok(true) = self.lookup_private_repo() => {
                 tracing::debug!("private: true");
+                println!("private: true");
                 return Ok(true);
             }
 
diff --git a/tests/dockerhub.rs b/tests/dockerhub.rs
index 79d46e67..d0975bd2 100644
--- a/tests/dockerhub.rs
+++ b/tests/dockerhub.rs
@@ -10,7 +10,7 @@ use serde_yaml;
 use stacker::forms::project::Volume;
 
 const DOCKER_USERNAME: &str = "trydirect";
-const DOCKER_PASSWORD: &str = "***************";
+const DOCKER_PASSWORD: &str = "**********";
 //  Unit Test
 
 // #[test]
@@ -105,14 +105,28 @@ async fn test_docker_non_existent_repo() {
 
     common::spawn_app().await; // server
     let di = DockerImage {
-        dockerhub_user: Some(String::from("trydirect")),
-        dockerhub_name: Some(String::from("nonexistent")),
-        dockerhub_image: None,
+        dockerhub_user: Some(String::from("trydirect")), //namespace
+        dockerhub_name: Some(String::from("nonexistent")), //repo
+        dockerhub_image: None, // namesps/reponame:tag full docker image string
         dockerhub_password: Some(String::from(""))
     };
+    println!("{}", di.is_active().await.unwrap());
     assert_eq!(di.is_active().await.unwrap(), false);
 }
 
+#[tokio::test]
+async fn test_docker_non_existent_repo_empty_namespace() {
+
+    common::spawn_app().await; // server
+    let di = DockerImage {
+        dockerhub_user: Some(String::from("")), //namespace
+        dockerhub_name: Some(String::from("nonexistent")), //repo
+        dockerhub_image: None, // namesps/reponame:tag full docker image string
+        dockerhub_password: Some(String::from(""))
+    };
+    assert_eq!(di.is_active().await.unwrap(), true);
+}
+
 #[tokio::test]
 async fn test_docker_named_volume() {
     let volume = Volume {

From 8eeddc4638a5d4e09971346391b5fe7f7a5b0f4e Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Thu, 13 Jun 2024 17:24:28 +0300
Subject: [PATCH 283/284] add 'command' instruction to docker-compose generator

---
 Dockerfile               | 2 +-
 docker-compose.yml       | 2 +-
 src/forms/project/app.rs | 2 ++
 3 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/Dockerfile b/Dockerfile
index 8d969e75..3523dd33 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -32,7 +32,7 @@ COPY ./src ./src
 #RUN cargo sqlx prepare -- --bin stacker
 
 RUN apt-get update && apt-get install --no-install-recommends -y libssl-dev; \
-    cargo build --bin=console && cargo build --release
+    cargo build --bin=console --features="explain" && cargo build --release --features="explain"
 
 #RUN ls -la /app/target/release/ >&2
 
diff --git a/docker-compose.yml b/docker-compose.yml
index 808216e8..66b2c45f 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -11,7 +11,7 @@ volumes:
 services:
 
   stacker:
-    image: trydirect/stacker:0.0.8
+    image: trydirect/stacker:0.0.9
     build: .
     container_name: stacker
     restart: always
diff --git a/src/forms/project/app.rs b/src/forms/project/app.rs
index 56a5da99..c78ea03b 100644
--- a/src/forms/project/app.rs
+++ b/src/forms/project/app.rs
@@ -63,6 +63,7 @@ pub struct App {
     pub url_git: Option<String>,
     #[validate(enumerate("always", "no", "unless-stopped", "on-failure"))]
     pub restart: String,
+    pub command: String,
     pub volumes: Option<Vec<forms::project::Volume>>,
     #[serde(flatten)]
     pub environment: forms::project::Environment,
@@ -144,6 +145,7 @@ impl App {
 
         service.ports = dctypes::Ports::Long(ports);
         service.restart = Some(self.restart.clone());
+        service.command = Some(dctypes::Command::Simple(self.command.clone()));
         service.volumes = volumes;
         service.environment = dctypes::Environment::KvPair(envs);
 

From bf0181a60c135384a3f3f2e5836a3ba2011cd2fc Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Mon, 17 Jun 2024 10:16:51 +0300
Subject: [PATCH 284/284] docker compose 'command' added when non empty, vendor
 public repo check fix

---
 .pre-commit-config.yaml  |  7 +++++
 src/forms/project/app.rs |  8 +++--
 src/helpers/dockerhub.rs | 67 +++++++++++++++++++++++++++++++++++++---
 3 files changed, 75 insertions(+), 7 deletions(-)
 create mode 100644 .pre-commit-config.yaml

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 00000000..99ebb1cc
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,7 @@
+repos:
+  - repo: https://github.com/gitguardian/ggshield
+    rev: v1.28.0
+    hooks:
+      - id: ggshield
+        language_version: python3
+        stages: [commit]
diff --git a/src/forms/project/app.rs b/src/forms/project/app.rs
index c78ea03b..b2468297 100644
--- a/src/forms/project/app.rs
+++ b/src/forms/project/app.rs
@@ -63,7 +63,7 @@ pub struct App {
     pub url_git: Option<String>,
     #[validate(enumerate("always", "no", "unless-stopped", "on-failure"))]
     pub restart: String,
-    pub command: String,
+    pub command: Option<String>,
     pub volumes: Option<Vec<forms::project::Volume>>,
     #[serde(flatten)]
     pub environment: forms::project::Environment,
@@ -145,7 +145,11 @@ impl App {
 
         service.ports = dctypes::Ports::Long(ports);
         service.restart = Some(self.restart.clone());
-        service.command = Some(dctypes::Command::Simple(self.command.clone()));
+        if let Some(cmd) = self.command.as_deref() {
+            if !cmd.is_empty() {
+                service.command = Some(dctypes::Command::Simple(cmd.to_owned()));
+            }
+        }
         service.volumes = volumes;
         service.environment = dctypes::Environment::KvPair(envs);
 
diff --git a/src/helpers/dockerhub.rs b/src/helpers/dockerhub.rs
index 64b88329..5b54d9a4 100644
--- a/src/helpers/dockerhub.rs
+++ b/src/helpers/dockerhub.rs
@@ -125,7 +125,7 @@ impl<'a> DockerHub<'a> {
             .json::<DockerHubToken>()
             .await
             .map(|dockerHubToken| dockerHubToken.token)
-            .map_err(|err| format!("{:?}", err))
+            .map_err(|err| format!("🟥 {:?}", err))
     }
 
     #[tracing::instrument(name = "Lookup public repos")]
@@ -155,11 +155,13 @@ impl<'a> DockerHub<'a> {
             .map(|repositories| {
                 tracing::debug!("Get public image repo {:?} response {:?}", &url, repositories);
                 if repositories.count.unwrap_or(0) > 0 {
-                    // let's find at least one active tag
+                    // let's find at least one active repo
                     let active = repositories
                         .results
                         .into_iter()
-                        .any(|repo| repo.status == Some(1));
+                        .any(|repo| {
+                            repo.status == Some(1)
+                        } );
                     tracing::debug!("✅ Public image is active. url: {:?}", &url);
                     active
                 } else {
@@ -194,7 +196,7 @@ impl<'a> DockerHub<'a> {
                         .into_iter()
                         .any(|tag| {
                             tracing::debug!("official: {:?}", tag);
-                            if "active".to_string() == tag.tag_status {
+                            if "active".to_string() == tag.tag_status  && tag.name.eq(self.tag.as_deref().unwrap_or("latest")) {
                                 true
                             } else {
                                 false
@@ -209,6 +211,50 @@ impl<'a> DockerHub<'a> {
             })
     }
 
+    #[tracing::instrument(name = "Lookup vendor's public repos")]
+    pub async fn lookup_vendor_public_repos(&'a self) -> Result<bool, String> {
+
+        let url = format!(
+            "https://hub.docker.com/v2/namespaces/{}/repositories/{}/tags",
+            &self.creds.username, &self.repos
+        );
+
+        tracing::debug!("Search vendor's public repos {:?}", url);
+        let client = reqwest::Client::new()
+            .get(url)
+            .header("Accept", "application/json");
+
+        client
+            .send()
+            .await
+            .map_err(|err| format!("🟥{}", err))?
+            .json::<TagResult>()
+            .await
+            .map_err(|err| {
+                tracing::debug!("🟥Error response {:?}", err);
+                format!("{}", err)
+            })
+            .map(|tags| {
+                tracing::debug!("Validate vendor's public image response {:?}", tags);
+                if tags.count.unwrap_or(0) > 0 {
+                    // let's find at least one active tag
+                    let t = match self.tag.clone() {
+                        Some(s) if !s.is_empty() => s,
+                        _ => String::from("latest"),
+                    };
+                    tracing::debug!("🟥 🟥 🟥 t={:?}", t);
+
+                    let active = tags
+                        .results
+                        .into_iter()
+                        .any(|tag| tag.tag_status.contains("active") && tag.name.eq(&t));
+                    return active;
+                } else {
+                    tracing::debug!("🟥 Image tag is not active");
+                    false
+                }
+            })
+    }
     #[tracing::instrument(name = "Lookup private repos")]
     pub async fn lookup_private_repo(&'a self) -> Result<bool, String> {
         let token = self.login().await?;
@@ -237,10 +283,15 @@ impl<'a> DockerHub<'a> {
                 tracing::debug!("Validate private image response {:?}", tags);
                 if tags.count.unwrap_or(0) > 0 {
                     // let's find at least one active tag
+                    let t = match self.tag.clone() {
+                        Some(s) if !s.is_empty() => s,
+                        _ => String::from("latest")
+                    };
+
                     let active = tags
                         .results
                         .into_iter()
-                        .any(|tag| tag.tag_status.contains("active"));
+                        .any(|tag| tag.tag_status.contains("active") && tag.name.eq(&t));
                     return active;
                 } else {
                     tracing::debug!("🟥 Image tag is not active");
@@ -283,6 +334,12 @@ impl<'a> DockerHub<'a> {
                 return Ok(true);
             }
 
+            Ok(true) = self.lookup_vendor_public_repos()  => {
+                tracing::debug!("public: true");
+                println!("public: true");
+                return Ok(true);
+            }
+
             Ok(true) = self.lookup_private_repo() => {
                 tracing::debug!("private: true");
                 println!("private: true");
