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 01/13] 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 5a510846c1e445a125115fdcdb736255b3dd06bc Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Thu, 29 Feb 2024 14:30:39 +0200
Subject: [PATCH 02/13] 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 03/13] 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 04/13] 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 05/13] 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 9f92af23c35300c69b5b7b1b27dc3355a23cfc87 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Mon, 4 Mar 2024 10:27:41 +0200
Subject: [PATCH 06/13] 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 07/13] 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 4c152e9ebde4268543d4ae252de513761a3d91a5 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Mon, 4 Mar 2024 18:15:09 +0200
Subject: [PATCH 08/13] 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 09/13] 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 10/13] 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 2cf6a7599c63b825f6e0b32c3fccb82431126981 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Tue, 12 Mar 2024 16:58:54 +0200
Subject: [PATCH 11/13] 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 d89fb335de32db02c9a8aa31294997de6346ba27 Mon Sep 17 00:00:00 2001
From: vsilent <jabberroid@gmail.com>
Date: Fri, 15 Mar 2024 17:10:23 +0200
Subject: [PATCH 12/13] 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 13/13] 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": ""
 }
 
