From 421f940eafc77e523d55d9fcb7f8b0fa6e300f0f Mon Sep 17 00:00:00 2001 From: vsilent Date: Sun, 5 Nov 2023 09:19:48 +0200 Subject: [PATCH 01/18] rename default to list_handler --- src/routes/rating/get.rs | 2 +- src/routes/stack/get.rs | 2 +- src/startup.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/routes/rating/get.rs b/src/routes/rating/get.rs index 011f943..2f63c76 100644 --- a/src/routes/rating/get.rs +++ b/src/routes/rating/get.rs @@ -69,7 +69,7 @@ pub async fn get_handler( #[tracing::instrument(name = "Get all ratings.")] #[get("")] -pub async fn default( +pub async fn list_handler( path: web::Path<()>, pool: web::Data, ) -> Result { diff --git a/src/routes/stack/get.rs b/src/routes/stack/get.rs index 17f9181..1497f9e 100644 --- a/src/routes/stack/get.rs +++ b/src/routes/stack/get.rs @@ -24,7 +24,7 @@ pub async fn get( let (id,) = path.into_inner(); - tracing::info!("User {:?} is getting stack by id {:?}", user, id); + tracing::info!("User {:?} gets stack by id {:?}", user.id, id); match sqlx::query_as!( models::Stack, r#" diff --git a/src/startup.rs b/src/startup.rs index e6115cf..edd58c1 100644 --- a/src/startup.rs +++ b/src/startup.rs @@ -112,7 +112,7 @@ pub async fn run( .wrap(Cors::permissive()) .service(crate::routes::rating::add_handler) .service(crate::routes::rating::get_handler) - .service(crate::routes::rating::default), + .service(crate::routes::rating::list_handler), ) // .service( // web::resource("/stack/{id}") From 1a5016016860eebc9c0724d316c448490a582314 Mon Sep 17 00:00:00 2001 From: vsilent Date: Mon, 6 Nov 2023 14:02:57 +0200 Subject: [PATCH 02/18] Proposal to use unified JsonResponse implementation, this should reduce the number of lines in the response code --- custom-stack-payload-4.json | 1 + ...0230905145525_creating_stack_tables.up.sql | 5 +- src/forms/user.rs | 56 +++---- src/helpers/json.rs | 156 ++++++++++++++++-- src/routes/client/add.rs | 50 ++---- src/routes/rating/add.rs | 44 ++--- src/routes/rating/get.rs | 75 +++------ src/routes/stack/add.rs | 41 +---- src/routes/stack/get.rs | 48 ++---- 9 files changed, 243 insertions(+), 233 deletions(-) create mode 100644 custom-stack-payload-4.json diff --git a/custom-stack-payload-4.json b/custom-stack-payload-4.json new file mode 100644 index 0000000..4f0ce61 --- /dev/null +++ b/custom-stack-payload-4.json @@ -0,0 +1 @@ +{"commonDomain":"","domainList":{},"region":"fsn1","zone":null,"server":"cx21","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":["stack_migration"],"save_token":false,"cloud_token":"r6LAjqrynVt7pUwctVkzBlJmKjLOCxJIWjZFMLTkPYCCB4rsgphhEVhiL4DuO757","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"name":"Smarty Bot","code":"smarty-bot","domain":"smartybot.xyz","sharedPorts":["8000"],"versions":[],"custom":true,"type":"web","main":true,"_id":"lltkpq6p347kystct","dockerhub_user":"trydirect","dockerhub_name":"smarty-bot","url_app":"smartybot.xyz","url_git":"https://github.com/vsilent/smarty.git","disk_size":"1Gb","ram_size":"1Gb","cpu":1}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"

Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.6","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","versions":[{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}],"domain":"","sharedPorts":["9000"],"main":true,"version":{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}}],"service":[{"_etag":null,"_id":230,"_created":"2023-05-24T12:51:52.108972","_updated":"2023-08-04T12:18:34.670194","name":"pgrst","code":"pgrst","role":null,"type":"service","default":null,"popularity":null,"descr":null,"ports":null,"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":null,"category_id":null,"parent_app_id":null,"full_description":null,"description":"

PostgREST description

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"1","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"pgrst","versions":[{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"},{"_etag":null,"_id":563,"_created":null,"_updated":"2023-05-24T12:52:15.351522","app_id":230,"name":"0.0.5","version":"0.0.5","update_status":"ready_for_testing","tag":"0.0.5"}],"domain":"","sharedPorts":["9999"],"main":true,"version":{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"}}],"servers_count":3,"custom_stack_name":"mysampleproject2","custom_stack_code":"another-bot2","custom_stack_category":["New"],"custom_stack_short_description":"sample short description","custom_stack_description":"stack description","custom_stack_publish":false,"project_name":"Smarty Bot","project_git_url":"https://github.com/vsilent/smarty.git","project_overview":"my product 1","project_description":"my product 1"}} diff --git a/migrations/20230905145525_creating_stack_tables.up.sql b/migrations/20230905145525_creating_stack_tables.up.sql index e908e97..c2d248c 100644 --- a/migrations/20230905145525_creating_stack_tables.up.sql +++ b/migrations/20230905145525_creating_stack_tables.up.sql @@ -1,12 +1,13 @@ -- Add up migration script here -- Add migration script here CREATE TABLE user_stack ( - id serial, + id serial4 NOT NULL, stack_id uuid NOT NULL, user_id VARCHAR(50) NOT NULL, name TEXT NOT NULL UNIQUE, body JSON NOT NULL, created_at timestamptz NOT NULL, - updated_at timestamptz NOT NULL + updated_at timestamptz NOT NULL, + CONSTRAINT user_stack_pkey PRIMARY KEY (id), ) diff --git a/src/forms/user.rs b/src/forms/user.rs index 2da0dcf..8ff0b4c 100644 --- a/src/forms/user.rs +++ b/src/forms/user.rs @@ -16,45 +16,45 @@ pub struct User { #[serde(rename = "_id")] pub id: String, #[serde(rename = "first_name")] - pub first_name: String, + pub first_name: Option, #[serde(rename = "last_name")] - pub last_name: String, - pub created: String, - pub updated: String, + pub last_name: Option, + pub created: Option, + pub updated: Option, pub email: String, #[serde(rename = "email_confirmed")] pub email_confirmed: bool, pub social: bool, - pub website: String, + pub website: Option, pub currency: Value, - pub phone: String, + pub phone: Option, #[serde(rename = "password_change_required")] pub password_change_required: Value, - pub photo: String, - pub country: String, + pub photo: Option, + pub country: Option, #[serde(rename = "billing_first_name")] pub billing_first_name: Value, #[serde(rename = "billing_last_name")] pub billing_last_name: Value, #[serde(rename = "billing_postcode")] - pub billing_postcode: String, + pub billing_postcode: Option, #[serde(rename = "billing_address_1")] - pub billing_address_1: String, + pub billing_address_1: Option, #[serde(rename = "billing_address_2")] - pub billing_address_2: String, + pub billing_address_2: Option, #[serde(rename = "billing_city")] - pub billing_city: String, + pub billing_city: Option, #[serde(rename = "billing_country_code")] - pub billing_country_code: String, + pub billing_country_code: Option, #[serde(rename = "billing_country_area")] - pub billing_country_area: String, - pub tokens: Vec, - pub subscriptions: Vec, - pub plan: Plan, + pub billing_country_area: Option, + pub tokens: Option>, + pub subscriptions: Option>, + pub plan: Option, #[serde(rename = "deployments_left")] pub deployments_left: Value, #[serde(rename = "suspension_hints")] - pub suspension_hints: SuspensionHints, + pub suspension_hints: Option, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -72,9 +72,9 @@ pub struct Subscription { #[serde(rename = "user_id")] pub user_id: i64, #[serde(rename = "date_created")] - pub date_created: String, + pub date_created: Option, #[serde(rename = "date_updated")] - pub date_updated: String, + pub date_updated: Option, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -92,21 +92,21 @@ pub struct Plan { pub billing_email: String, #[serde(rename = "date_of_purchase")] pub date_of_purchase: String, - pub currency: String, - pub price: String, - pub period: String, + pub currency: Option, + pub price: Option, + pub period: Option, #[serde(rename = "date_start")] pub date_start: String, pub active: bool, #[serde(rename = "billing_id")] - pub billing_id: String, + pub billing_id: Option, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SupportedStacks { - pub monthly: i64, - pub annually: i64, + pub monthly: Option, + pub annually: Option, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -132,8 +132,8 @@ impl TryInto for UserForm { // )?; Ok(UserModel { id: self.user.id, - first_name: self.user.first_name, - last_name: self.user.last_name, + first_name: self.user.first_name.unwrap_or("Noname".to_string()), + last_name: self.user.last_name.unwrap_or("Noname".to_string()), email: self.user.email, email_confirmed: self.user.email_confirmed, }) diff --git a/src/helpers/json.rs b/src/helpers/json.rs index c0a6545..ad18305 100644 --- a/src/helpers/json.rs +++ b/src/helpers/json.rs @@ -1,41 +1,161 @@ -// use std::collections::HashMap; -// use actix_web::{HttpRequest, HttpResponse, Responder}; +use actix_web::{HttpRequest, HttpResponse, Responder}; use serde_derive::Serialize; +use crate::models; -#[derive(Serialize)] -pub(crate) struct JsonResponse { +#[derive(Serialize, Default)] +pub(crate) struct JsonResponse { pub(crate) status: String, pub(crate) message: String, pub(crate) code: u32, pub(crate) id: Option, + pub(crate) item: Option, + pub(crate) list: Option> } - +// // #[derive(Serialize)] -// pub(crate) struct JsonResponse { +// pub(crate) struct JsonErrorResponse { // pub(crate) status: String, // pub(crate) message: String, // pub(crate) code: u32, -// pub(crate) custom_fields: HashMap>, +// pub(crate) id: Option, +// pub(crate) item: Option, +// pub(crate) list: Option> // } + +impl JsonResponse { + pub(crate) fn new(status: String, + message: String, + code: u32, + id: Option, + item:Option, + list: Option>) -> Self { + tracing::debug!("Executed.."); + JsonResponse { + status, + message, + code, + id, + item, + list, + } + } + + pub(crate) fn ok(id: i32, message: &str) -> JsonResponse { + + let msg = if !message.trim().is_empty() { + message.to_string() + } + else{ + String::from("Success") + }; + + JsonResponse { + status: "OK".to_string(), + message: msg, + code: 200, + id: Some(id), + item: None, + list: None + } + } + + pub(crate) fn not_found() -> Self { + JsonResponse { + status: "Error".to_string(), + code: 404, + message: format!("Object not found"), + id: None, + item: None, + list: None + } + } + + pub(crate) fn internal_error(message: &str) -> Self { + + let msg = if !message.trim().is_empty() { + message.to_string() + } + else{ + String::from("Internal error") + }; + JsonResponse { + status: "Error".to_string(), + code: 500, + message: msg, + id: None, + item: None, + list: None + } + } + + pub(crate) fn not_valid(message: &str) -> Self { + + let msg = if !message.trim().is_empty() { + message.to_string() + } + else{ + String::from("Validation error") + }; + JsonResponse { + status: "Error".to_string(), + code: 400, + message: msg, + id: None, + item: None, + list: None + } + } +} + +// // Implement the Responder trait for GlobalResponse +// impl Responder for JsonResponse where T: { +// +// type Body = (); // -// impl JsonResponse { -// pub(crate) fn new(status: String, message: String, code: u32) -> Self { -// let custom_fields = HashMap::new(); -// JsonResponse { +// fn respond_to(self, _req: &HttpRequest) -> HttpResponse { +// HttpResponse::Ok().json(self) +// } +// } +// +// impl JsonErrorResponse { +// pub(crate) fn new(status: String, +// message: String, +// code: u32, +// id: Option, +// item:Option, +// list: Option>) -> Self { +// JsonErrorResponse { // status, // message, // code, -// custom_fields +// id, +// item, +// list, // } // } -// } // -// // Implement the Responder trait for GlobalResponse -// impl Responder for JsonResponse { -// type Body = (); +// pub(crate) fn default() -> Self { +// JsonErrorResponse { +// status: "Internal Error".to_string(), +// message: "Internal Error".to_string(), +// code: 500, +// id: None, +// item: None, +// list: None, +// } +// } // -// fn respond_to(self, _req: &HttpRequest) -> HttpResponse { -// HttpResponse::Ok().json(self) +// pub(crate) fn not_found() -> Self { +// JsonErrorResponse { +// status: "Error".to_string(), +// code: 404, +// message: format!("Object not found"), +// id: None, +// item: None, +// list: None +// } // } +// +// // } diff --git a/src/routes/client/add.rs b/src/routes/client/add.rs index 0f5435c..ae822f8 100644 --- a/src/routes/client/add.rs +++ b/src/routes/client/add.rs @@ -1,20 +1,12 @@ use crate::configuration::Settings; -use crate::helpers::client; +use crate::helpers::{client, JsonResponse}; use crate::models::user::User; use crate::models::Client; use actix_web::{post, web, Responder, Result}; -use serde::Serialize; use sqlx::PgPool; use std::sync::Arc; use tracing::Instrument; -#[derive(Serialize)] -struct ClientAddResponse { - status: String, - message: String, - code: u32, - client: Option, -} #[tracing::instrument(name = "Add client.")] #[post("")] @@ -24,6 +16,7 @@ pub async fn add_handler( pool: web::Data, ) -> Result { let query_span = tracing::info_span!("Counting the user's clients"); + match sqlx::query!( r#" SELECT @@ -46,23 +39,14 @@ pub async fn add_handler( client_count ); - return Ok(web::Json(ClientAddResponse { - status: "error".to_string(), - code: 400, - message: "Too many clients already created".to_string(), - client: None, - })); + return Ok(web::Json(JsonResponse::not_valid( + "Too many clients already created")) + ); } } Err(e) => { tracing::error!("Failed to execute query: {:?}", e); - - return Ok(web::Json(ClientAddResponse { - status: "error".to_string(), - code: 500, - message: "Failed to insert".to_string(), - client: None, - })); + return Ok(web::Json(JsonResponse::internal_error("Failed to insert"))); } }; @@ -88,22 +72,18 @@ pub async fn add_handler( Ok(result) => { tracing::info!("New client {} have been saved to database", result.id); client.id = result.id; - Ok(web::Json(ClientAddResponse { - status: "success".to_string(), - message: "".to_string(), - code: 200, - client: Some(client), - })) + Ok(web::Json(JsonResponse::new( + "success".to_string(), + "".to_string(), + 200, + Some(client.id), + Some(client), + None + ))) } Err(e) => { tracing::error!("Failed to execute query: {:?}", e); - - return Ok(web::Json(ClientAddResponse { - status: "error".to_string(), - code: 500, - message: "Failed to insert".to_string(), - client: None, - })); + Ok(web::Json(JsonResponse::internal_error("Failed to insert"))) } } } diff --git a/src/routes/rating/add.rs b/src/routes/rating/add.rs index cd7fa2b..382b564 100644 --- a/src/routes/rating/add.rs +++ b/src/routes/rating/add.rs @@ -13,6 +13,7 @@ use tracing::Instrument; // ACL - access to func for a user // ACL - access to objects for a user + #[tracing::instrument(name = "Add rating.")] #[post("")] pub async fn add_handler( @@ -35,12 +36,7 @@ pub async fn add_handler( } Err(e) => { tracing::error!("Failed to fetch product: {:?}, error: {:?}", form.obj_id, e); - return Ok(web::Json(JsonResponse { - status: "Error".to_string(), - code: 404, - message: format!("Object not found {}", form.obj_id), - id: None, - })); + return Ok(web::Json(JsonResponse::::not_found())); } }; @@ -64,22 +60,19 @@ pub async fn add_handler( form.category ); - return Ok(web::Json(JsonResponse { - status: "Error".to_string(), - code: 409, - message: format!("Already Rated"), - id: Some(record.id), - })); + return Ok(web::Json(JsonResponse::::new( + "Error".to_string(), + "Already rated".to_string(), + 409, + Some(record.id), + None, + None + ))); } Err(sqlx::Error::RowNotFound) => {} Err(e) => { tracing::error!("Failed to fetch rating, error: {:?}", e); - return Ok(web::Json(JsonResponse { - status: "Error".to_string(), - code: 500, - message: format!("Internal Server Error"), - id: None, - })); + return Ok(web::Json(JsonResponse::::internal_error(""))); } } @@ -106,22 +99,11 @@ pub async fn add_handler( { Ok(result) => { tracing::info!("New rating {} have been saved to database", result.id); - - Ok(web::Json(JsonResponse { - status: "ok".to_string(), - code: 200, - message: "Saved".to_string(), - id: Some(result.id), - })) + return Ok(web::Json(JsonResponse::::ok(result.id, "Saved"))); } Err(e) => { tracing::error!("Failed to execute query: {:?}", e); - Ok(web::Json(JsonResponse { - status: "error".to_string(), - code: 500, - message: "Failed to insert".to_string(), - id: None, - })) + return Ok(web::Json(JsonResponse::internal_error("Failed to insert"))); } } } diff --git a/src/routes/rating/get.rs b/src/routes/rating/get.rs index 2f63c76..19d9399 100644 --- a/src/routes/rating/get.rs +++ b/src/routes/rating/get.rs @@ -1,22 +1,15 @@ use crate::models; use actix_web::{get, web, Responder, Result}; -use serde_derive::Serialize; use sqlx::PgPool; use tracing::Instrument; +use crate::helpers::JsonResponse; +use crate::models::user::User; // workflow // add, update, list, get(user_id), ACL, // ACL - access to func for a user // ACL - access to objects for a user -#[derive(Serialize)] -struct JsonResponse { - status: String, - message: String, - code: u32, - rating: Option, - objects: Option>, -} #[tracing::instrument(name = "Get rating.")] #[get("/{id}")] @@ -24,6 +17,7 @@ pub async fn get_handler( path: web::Path<(i32,)>, pool: web::Data, ) -> Result { + /// Get rating of any user let rate_id = path.0; let query_span = tracing::info_span!("Search for rate id={}.", rate_id); match sqlx::query_as!( @@ -37,32 +31,21 @@ pub async fn get_handler( { Ok(rating) => { tracing::info!("rating found: {:?}", rating.id,); - return Ok(web::Json(JsonResponse { - status: "Success".to_string(), - code: 200, - message: "".to_string(), - rating: Some(rating), - objects: None - })); + return Ok(web::Json(JsonResponse::new( + "Success".to_string(), + "Rating found".to_string(), + 200, + Some(rating.id), + Some(rating), + None + ))); } Err(sqlx::Error::RowNotFound) => { - return Ok(web::Json(JsonResponse { - status: "Error".to_string(), - code: 404, - message: format!("Not Found"), - rating: None, - objects: None - })); + return Ok(web::Json(JsonResponse::not_found())); } Err(e) => { tracing::error!("Failed to fetch rating, error: {:?}", e); - return Ok(web::Json(JsonResponse { - status: "Error".to_string(), - code: 500, - message: format!("Internal Server Error"), - rating: None, - objects: None - })); + return Ok(web::Json(JsonResponse::internal_error(""))); } } } @@ -73,6 +56,7 @@ pub async fn list_handler( path: web::Path<()>, pool: web::Data, ) -> Result { + /// Get ratings of all users let query_span = tracing::info_span!("Get all rates."); // let category = path.0; @@ -86,32 +70,21 @@ pub async fn list_handler( { Ok(rating) => { tracing::info!("Ratings found: {:?}", rating.len()); - return Ok(web::Json(JsonResponse { - status: "Success".to_string(), - code: 200, - message: "".to_string(), - rating: None, - objects: Some(rating), - })); + return Ok(web::Json(JsonResponse::new( + "Success".to_string(), + "".to_string(), + 200, + None, + None, + Some(rating), + ))); } Err(sqlx::Error::RowNotFound) => { - return Ok(web::Json(JsonResponse { - status: "Error".to_string(), - code: 404, - message: format!("Not Found"), - rating: None, - objects: None - })); + return Ok(web::Json(JsonResponse::not_found())); } Err(e) => { tracing::error!("Failed to fetch rating, error: {:?}", e); - return Ok(web::Json(JsonResponse { - status: "Error".to_string(), - code: 500, - message: format!("Internal Server Error"), - rating: None, - objects: None - })); + return Ok(web::Json(JsonResponse::internal_error(""))); } } } diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs index 53eb389..d1d8b87 100644 --- a/src/routes/stack/add.rs +++ b/src/routes/stack/add.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use actix_web::{ web, web::{Bytes, Data, Json}, @@ -17,6 +16,8 @@ use std::str; use tracing::Instrument; use uuid::Uuid; + + #[tracing::instrument(name = "Add stack.")] #[post("")] pub async fn add( @@ -24,10 +25,6 @@ pub async fn add( user: web::ReqData, pool: Data, ) -> Result { - // None::.expect("my error"); - // return Err(JsonPayloadError::Payload(PayloadError::Overflow).into()); - // let content_type = req.headers().get("content-type"); - // println!("Request Content-Type: {:?}", content_type); let body_bytes = actix_web::body::to_bytes(body).await.unwrap(); let body_str = str::from_utf8(&body_bytes).unwrap(); @@ -35,19 +32,12 @@ pub async fn add( // method 2 let app_state = serde_json::from_str::(body_str).unwrap(); let form = match serde_json::from_str::(body_str) { Ok(f) => { - println!("fine"); f } Err(err) => { - return Ok(Json(JsonResponse { - status: "Error".to_string(), - code: 400, - message: err.to_string(), - id: None, - })); + return Ok(Json(JsonResponse::::not_valid(""))); } }; - // println!("app: {:?}", form); let user_id = user.id.clone(); let request_id = Uuid::new_v4(); @@ -81,16 +71,14 @@ pub async fn add( let stack = Stack { id: 0_i32, // internal stack id stack_id: Uuid::new_v4(), // public uuid of the stack - // user_id: Uuid::from_u128(user_id as u128), - user_id: user_id, // + user_id: user_id, // name: stack_name, body: body, - // body: body_str.to_string(), created_at: Utc::now(), updated_at: Utc::now(), }; - println!("stack object {:?}", stack); + tracing::debug!("stack object {:?}", stack); return match sqlx::query!( r#" INSERT INTO user_stack (id, stack_id, user_id, name, body, created_at, updated_at) @@ -101,7 +89,6 @@ pub async fn add( stack.stack_id, stack.user_id, stack.name, - // sqlx::types::Json(stack.body), stack.body, stack.created_at, stack.updated_at @@ -115,21 +102,11 @@ pub async fn add( "req_id: {} New stack details have been saved to database", request_id ); - Ok(Json(JsonResponse { - status: "OK".to_string(), - code: 200, - message: format!("Object saved"), - id: Some(record.id), - })) + Ok(Json(JsonResponse::::ok(record.id, "Object saved"))) } - Err(e) => { - tracing::error!("req_id: {} Failed to execute query: {:?}", request_id, e); - Ok(Json(JsonResponse { - status: "Error".to_string(), - code: 400, - message: e.to_string(), - id: None, - })) + Err(err) => { + tracing::error!("req_id: {} Failed to execute query: {:?}", request_id, err); + Ok(Json(JsonResponse::::not_valid("Failed to insert"))) } }; } diff --git a/src/routes/stack/get.rs b/src/routes/stack/get.rs index 1497f9e..7b5fa7f 100644 --- a/src/routes/stack/get.rs +++ b/src/routes/stack/get.rs @@ -1,26 +1,18 @@ use actix_web::{web, get, Responder, Result}; -use serde_derive::Serialize; use sqlx::PgPool; +use crate::helpers::JsonResponse; use crate::models; use crate::models::user::User; -#[derive(Serialize)] -struct JsonResponse { - status: String, - message: String, - code: u32, - id: Option, - object: Option, - objects: Option>, -} -#[tracing::instrument(name = "Get stack.")] +#[tracing::instrument(name = "Get logged user stack.")] #[get("/{id}")] pub async fn get( user: web::ReqData, path: web::Path<(i32,)>, pool: web::Data, ) -> Result { + /// Get stack apps of logged user only let (id,) = path.into_inner(); @@ -37,36 +29,20 @@ pub async fn get( { Ok(stack) => { tracing::info!("stack found: {:?}", stack.id,); - let response = JsonResponse { - status: "Success".to_string(), - code: 200, - message: "".to_string(), - id: Some(stack.id), - object: Some(stack), - objects: None - }; - return Ok(web::Json(response)); + return Ok(web::Json(JsonResponse::::new( + "Success".to_string(), + "".to_string(), + 200, + Some(stack.id), + Some(stack), + None))); } Err(sqlx::Error::RowNotFound) => { - return Ok(web::Json(JsonResponse { - status: "Error".to_string(), - code: 404, - message: format!("Not Found"), - id: None, - object: None, - objects: None - })); + return Ok(web::Json(JsonResponse::::not_found())); } Err(e) => { tracing::error!("Failed to fetch stack, error: {:?}", e); - return Ok(web::Json(JsonResponse { - status: "Error".to_string(), - code: 500, - message: format!("Internal Server Error"), - id: None, - object: None, - objects: None - })); + return Ok(web::Json(JsonResponse::::internal_error(""))); } } } From 1dd107eddac19c8955b47d749b01edf01919b72f Mon Sep 17 00:00:00 2001 From: vsilent Date: Tue, 7 Nov 2023 14:34:42 +0200 Subject: [PATCH 03/18] dctypes lib added, ports converted --- Cargo.lock | 124 +++++- Cargo.toml | 12 + custom-stack-payload-4.json | 2 +- files/docker-compose.yml | 20 + src/helpers/mod.rs | 1 + src/helpers/stack/builder.rs | 207 +++++++++ src/helpers/stack/dctypes.rs | 842 +++++++++++++++++++++++++++++++++++ src/helpers/stack/mod.rs | 2 + src/models/stack.rs | 2 +- src/routes/stack/add.rs | 52 ++- src/routes/stack/get.rs | 1 + src/startup.rs | 1 + 12 files changed, 1259 insertions(+), 7 deletions(-) create mode 100644 files/docker-compose.yml create mode 100644 src/helpers/stack/builder.rs create mode 100644 src/helpers/stack/dctypes.rs create mode 100644 src/helpers/stack/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 21202d5..68ef0eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -562,6 +562,41 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + [[package]] name = "deranged" version = "0.3.9" @@ -571,6 +606,37 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive_builder" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder_macro" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" +dependencies = [ + "derive_builder_core", + "syn 1.0.109", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -645,6 +711,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.5" @@ -803,6 +875,12 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "h2" version = "0.3.21" @@ -815,7 +893,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.3", "slab", "tokio", "tokio-util", @@ -983,6 +1061,12 @@ dependencies = [ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.4.0" @@ -1004,6 +1088,17 @@ dependencies = [ "serde", ] +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown 0.14.1", + "serde", +] + [[package]] name = "instant" version = "0.1.12" @@ -1886,7 +1981,7 @@ version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0adc7a19d45e581abc6d169c865a0b14b84bb43a9e966d1cca4d733e70f7f35a" dependencies = [ - "indexmap", + "indexmap 1.9.3", "itertools 0.10.5", "num-traits", "once_cell", @@ -1924,6 +2019,19 @@ dependencies = [ "regex", ] +[[package]] +name = "serde_yaml" +version = "0.9.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574" +dependencies = [ + "indexmap 2.1.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha1" version = "0.10.6" @@ -2059,7 +2167,7 @@ dependencies = [ "hex", "hkdf", "hmac", - "indexmap", + "indexmap 1.9.3", "itoa", "libc", "log", @@ -2129,6 +2237,9 @@ dependencies = [ "actix-web-httpauth", "chrono", "config", + "derive_builder", + "glob", + "indexmap 2.1.0", "rand", "regex", "reqwest", @@ -2136,6 +2247,7 @@ dependencies = [ "serde_derive", "serde_json", "serde_valid", + "serde_yaml", "sqlx", "thiserror", "tokio", @@ -2545,6 +2657,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +[[package]] +name = "unsafe-libyaml" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" + [[package]] name = "untrusted" version = "0.7.1" diff --git a/Cargo.toml b/Cargo.toml index 47cbb10..581f4d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,11 @@ tracing-actix-web = "0.7.7" regex = "1.10.2" rand = "0.8.5" +# dctypes +derive_builder = "0.12.0" +indexmap = { version = "2.0.0", features = ["serde"], optional = true } +serde_yaml = "0.9" + [dependencies.sqlx] version = "0.6.3" features = [ @@ -45,3 +50,10 @@ features = [ "json", "offline" ] + +[features] +default = ["indexmap"] +indexmap = ["dep:indexmap"] + +[dev-dependencies] +glob = "0.3" \ No newline at end of file diff --git a/custom-stack-payload-4.json b/custom-stack-payload-4.json index 4f0ce61..07ee156 100644 --- a/custom-stack-payload-4.json +++ b/custom-stack-payload-4.json @@ -1 +1 @@ -{"commonDomain":"","domainList":{},"region":"fsn1","zone":null,"server":"cx21","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":["stack_migration"],"save_token":false,"cloud_token":"r6LAjqrynVt7pUwctVkzBlJmKjLOCxJIWjZFMLTkPYCCB4rsgphhEVhiL4DuO757","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"name":"Smarty Bot","code":"smarty-bot","domain":"smartybot.xyz","sharedPorts":["8000"],"versions":[],"custom":true,"type":"web","main":true,"_id":"lltkpq6p347kystct","dockerhub_user":"trydirect","dockerhub_name":"smarty-bot","url_app":"smartybot.xyz","url_git":"https://github.com/vsilent/smarty.git","disk_size":"1Gb","ram_size":"1Gb","cpu":1}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"

Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.6","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","versions":[{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}],"domain":"","sharedPorts":["9000"],"main":true,"version":{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}}],"service":[{"_etag":null,"_id":230,"_created":"2023-05-24T12:51:52.108972","_updated":"2023-08-04T12:18:34.670194","name":"pgrst","code":"pgrst","role":null,"type":"service","default":null,"popularity":null,"descr":null,"ports":null,"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":null,"category_id":null,"parent_app_id":null,"full_description":null,"description":"

PostgREST description

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"1","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"pgrst","versions":[{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"},{"_etag":null,"_id":563,"_created":null,"_updated":"2023-05-24T12:52:15.351522","app_id":230,"name":"0.0.5","version":"0.0.5","update_status":"ready_for_testing","tag":"0.0.5"}],"domain":"","sharedPorts":["9999"],"main":true,"version":{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"}}],"servers_count":3,"custom_stack_name":"mysampleproject2","custom_stack_code":"another-bot2","custom_stack_category":["New"],"custom_stack_short_description":"sample short description","custom_stack_description":"stack description","custom_stack_publish":false,"project_name":"Smarty Bot","project_git_url":"https://github.com/vsilent/smarty.git","project_overview":"my product 1","project_description":"my product 1"}} +{"commonDomain":"", "domainList":{}, "region":"fsn1", "zone":null, "server":"cx21", "os":"ubuntu-20.04", "ssl":"letsencrypt", "vars":[], "integrated_features":[],"extended_features":[],"subscriptions":["stack_migration"],"save_token":false,"cloud_token":"r6LAjqrynVt7pUwctVkzBlJmKjLOCxJIWjZFMLTkPYCCB4rsgphhEVhiL4DuO757","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"name":"Smarty Bot","code":"smarty-bot","domain":"smartybot.xyz","sharedPorts":["8000"],"versions":[],"custom":true,"type":"web","main":true,"_id":"lltkpq6p347kystct","dockerhub_user":"trydirect","dockerhub_name":"smarty-bot","url_app":"smartybot.xyz","url_git":"https://github.com/vsilent/smarty.git","disk_size":"1Gb","ram_size":"1Gb","cpu":1}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"

Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.6","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","versions":[{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}],"domain":"","sharedPorts":["9000"],"main":true,"version":{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}}],"service":[{"_etag":null,"_id":230,"_created":"2023-05-24T12:51:52.108972","_updated":"2023-08-04T12:18:34.670194","name":"pgrst","code":"pgrst","role":null,"type":"service","default":null,"popularity":null,"descr":null,"ports":null,"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":null,"category_id":null,"parent_app_id":null,"full_description":null,"description":"

PostgREST description

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"1","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"pgrst","versions":[{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"},{"_etag":null,"_id":563,"_created":null,"_updated":"2023-05-24T12:52:15.351522","app_id":230,"name":"0.0.5","version":"0.0.5","update_status":"ready_for_testing","tag":"0.0.5"}],"domain":"","sharedPorts":["9999"],"main":true,"version":{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"}}],"servers_count":3,"custom_stack_name":"mysampleproject2","custom_stack_code":"another-bot2","custom_stack_category":["New"],"custom_stack_short_description":"sample short description","custom_stack_description":"stack description","custom_stack_publish":false,"project_name":"Smarty Bot","project_git_url":"https://github.com/vsilent/smarty.git","project_overview":"my product 1","project_description":"my product 1"}} diff --git a/files/docker-compose.yml b/files/docker-compose.yml new file mode 100644 index 0000000..c6e60d6 --- /dev/null +++ b/files/docker-compose.yml @@ -0,0 +1,20 @@ +version: '3.8' +services: + smarty-bot: + image: trydirect/smarty-bot:latest + ports: + - target: 8000 + published: 8000 + restart: always + pgrst: + image: pgrst + ports: + - target: 9999 + published: 9999 + restart: always + portainer_ce_feature: + image: portainer-ce-feature + ports: + - target: 9000 + published: 9000 + restart: always diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs index 3500bbd..823b2b1 100644 --- a/src/helpers/mod.rs +++ b/src/helpers/mod.rs @@ -1,5 +1,6 @@ pub mod client; pub(crate) mod json; pub mod serialize_datetime; +pub(crate) mod stack; pub use json::*; diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs new file mode 100644 index 0000000..023ee96 --- /dev/null +++ b/src/helpers/stack/builder.rs @@ -0,0 +1,207 @@ +use crate::helpers::stack::dctypes::{Compose, Port, Ports, PublishedPort, Service, Services, SingleService}; +use serde_yaml; +use crate::forms; +use crate::forms::{StackForm, Web, Feature}; +use crate::models::stack::Stack; + +#[derive(Clone, Debug)] +struct Config { + +} + +impl Default for Config { + fn default() -> Self { + Config {} + } +} + +/// A builder for constructing docker compose. +#[derive(Clone, Debug)] +pub struct DcBuilder { + config: Config, + stack: Stack +} + +impl TryInto> for Web { + type Error = String; + fn try_into(self) -> Result, Self::Error> { + convert_shared_ports(self.shared_ports.clone().unwrap()) + } +} + +impl TryInto> for &Feature { + type Error = String; + fn try_into(self) -> Result, Self::Error> { + convert_shared_ports(self.shared_ports.clone().unwrap()) + } +} + +impl TryInto> for &forms::stack::Service { + type Error = String; + fn try_into(self) -> Result, Self::Error> { + convert_shared_ports(self.shared_ports.clone().unwrap()) + } +} + +fn convert_shared_ports(ports: Vec) -> Result, String> { + let mut _ports: Vec = vec![]; + for p in ports { + let port = p.parse::().map_err(|e| e.to_string())?; + _ports.push(Port { + target: port, + host_ip: None, + published: Some(PublishedPort::Single(port)), + protocol: None, + mode: None, + }); + } + Ok(_ports) +} + +impl DcBuilder { + + pub fn new() -> Self { + DcBuilder { + config: Config::default(), + stack: Stack { + id: 0, + stack_id: Default::default(), + user_id: "".to_string(), + name: "".to_string(), + body: Default::default(), + created_at: Default::default(), + updated_at: Default::default(), + }, + } + } + + // pub fn add_ports(&self, ports: &Vec) -> Vec { + // // @todo re-factor using TryInto or TryFrom + // + // let mut _ports:Vec = vec![]; + // for p in ports { + // let port = p.parse::().unwrap(); + // _ports.push( + // Port { + // target: port, + // host_ip: None, + // published: Some(PublishedPort::Single(port)), + // protocol: None, + // mode: None, + // } + // ); + // } + // _ports + // } + + pub fn build(&self, stack:Stack) -> Option { + + tracing::debug!("Start build docker compose from {:?}", stack.body); + let _stack = serde_json::from_value::(stack.body); + let mut services = indexmap::IndexMap::new(); + match _stack { + Ok(apps) => { + println!("stack item {:?}", apps.custom.web); + + for app in apps.custom.web { + // println!("app name {:?}", app.name); + let tag = "latest"; + let img= format!("{}/{}:{}",app.dockerhub_user, app.dockerhub_name, tag); + let code = app.code.clone().to_owned(); + let mut service = Service { + image: Some(img.to_string()), + ..Default::default() + }; + + if let Some(ports) = &app.shared_ports { + if !ports.is_empty() { + // service.ports = Ports::Long(self.add_ports(ports)); + service.ports = Ports::Long(app.try_into().unwrap()) + } + } + + service.restart = Some("always".to_owned()); + services.insert( + code, + Some(service), + ); + } + + if let Some(srvs) = &apps.custom.service { + + if !srvs.is_empty() { + + for app in srvs { + let code = app.code.to_owned(); + let tag = "latest"; + + let mut service = Service { + image: Some(app.dockerhub_image.as_ref().unwrap().to_owned()), + ..Default::default() + }; + + if let Some(ports) = &app.shared_ports { + if !ports.is_empty() { + // service.ports = Ports::Long(self.add_ports(ports)); + service.ports = Ports::Long(app.try_into().unwrap()) + } + } + service.restart = Some("always".to_owned()); + services.insert( + code, + Some(service), + ); + } + } + } + if let Some(features) = &apps.custom.feature { + + if !features.is_empty() { + + for app in features { + let code = app.code.to_owned(); + let mut service = Service { + image: Some(app.dockerhub_image.as_ref().unwrap().to_owned()), + ..Default::default() + }; + + if let Some(ports) = &app.shared_ports { + if !ports.is_empty() { + // service.ports = Ports::Long(self.add_ports(ports)); + service.ports = Ports::Long(app.try_into().unwrap()) + } + } + service.restart = Some("always".to_owned()); + services.insert( + code, + Some(service), + ); + } + } + } + } + Err(e) => { + () + } + } + + let compose_content = Compose { + version: Some("3.8".to_string()), + services: { + Services(services) + }, + ..Default::default() + }; + + let target_file = std::path::Path::new("./files/docker-compose.yml"); + // serialize to string + let serialized = match serde_yaml::to_string(&compose_content) { + Ok(s) => s, + Err(e) => panic!("Failed to serialize docker-compose file: {}", e), + }; + // serialize to file + std::fs::write(target_file, serialized).unwrap(); + + Some(compose_content) + } +} diff --git a/src/helpers/stack/dctypes.rs b/src/helpers/stack/dctypes.rs new file mode 100644 index 0000000..931deab --- /dev/null +++ b/src/helpers/stack/dctypes.rs @@ -0,0 +1,842 @@ +use derive_builder::*; +#[cfg(feature = "indexmap")] +use indexmap::IndexMap; +use serde::{Deserialize, Serialize}; +use serde_yaml::Value; +#[cfg(not(feature = "indexmap"))] +use std::collections::HashMap; +use std::convert::TryFrom; +use std::fmt; +use std::str::FromStr; + +#[allow(clippy::large_enum_variant)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum ComposeFile { + V2Plus(Compose), + #[cfg(feature = "indexmap")] + V1(IndexMap), + #[cfg(not(feature = "indexmap"))] + V1(HashMap), + Single(SingleService), +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)] +pub struct SingleService { + pub service: Service, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)] +pub struct Compose { + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, + #[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, + #[cfg(feature = "indexmap")] + #[serde(flatten, skip_serializing_if = "IndexMap::is_empty")] + pub extensions: IndexMap, + #[cfg(not(feature = "indexmap"))] + #[serde(flatten, skip_serializing_if = "HashMap::is_empty")] + pub extensions: HashMap, +} + +impl Compose { + pub fn new() -> Self { + Default::default() + } +} + +#[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, + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub privileged: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub healthcheck: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub deploy: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub image: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub container_name: Option, + #[serde(skip_serializing_if = "Option::is_none", rename = "build")] + pub build_: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub pid: Option, + #[serde(default, skip_serializing_if = "Ports::is_empty")] + pub ports: Ports, + #[serde(default, skip_serializing_if = "Environment::is_empty")] + pub environment: Environment, + #[serde(skip_serializing_if = "Option::is_none")] + pub network_mode: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub devices: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub restart: Option, + #[serde(default, skip_serializing_if = "Labels::is_empty")] + pub labels: Labels, + #[serde(skip_serializing_if = "Option::is_none")] + pub tmpfs: Option, + #[serde(default, skip_serializing_if = "Ulimits::is_empty")] + pub ulimits: Ulimits, + #[serde(default, skip_serializing_if = "Volumes::is_empty")] + pub volumes: Volumes, + #[serde(default, skip_serializing_if = "Networks::is_empty")] + pub networks: Networks, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub cap_add: Vec, + #[serde(default, skip_serializing_if = "DependsOnOptions::is_empty")] + pub depends_on: DependsOnOptions, + #[serde(skip_serializing_if = "Option::is_none")] + pub command: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub entrypoint: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub env_file: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub stop_grace_period: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub profiles: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub links: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub dns: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub ipc: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub net: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub stop_signal: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub user: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub working_dir: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub expose: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub volumes_from: Vec, + #[cfg(feature = "indexmap")] + #[serde(default, skip_serializing_if = "IndexMap::is_empty")] + pub extends: IndexMap, + #[cfg(not(feature = "indexmap"))] + #[serde(default, skip_serializing_if = "HashMap::is_empty")] + pub extends: HashMap, + #[serde(skip_serializing_if = "Option::is_none")] + pub logging: Option, + #[serde(default, skip_serializing_if = "is_zero")] + pub scale: i64, + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub init: bool, + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub stdin_open: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub shm_size: Option, + #[cfg(feature = "indexmap")] + #[serde(flatten, skip_serializing_if = "IndexMap::is_empty")] + pub extensions: IndexMap, + #[cfg(not(feature = "indexmap"))] + #[serde(flatten, skip_serializing_if = "HashMap::is_empty")] + pub extensions: HashMap, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub extra_hosts: Vec, + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub tty: bool, + #[serde(default, skip_serializing_if = "SysCtls::is_empty")] + pub sysctls: SysCtls, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub security_opt: Vec, +} + +impl Service { + pub fn image(&self) -> &str { + self.image.as_deref().unwrap_or_default() + } + + pub fn network_mode(&self) -> &str { + self.network_mode.as_deref().unwrap_or_default() + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)] +#[serde(untagged)] +pub enum EnvFile { + Simple(String), + List(Vec), +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] +#[serde(untagged)] +pub enum DependsOnOptions { + Simple(Vec), + #[cfg(feature = "indexmap")] + Conditional(IndexMap), + #[cfg(not(feature = "indexmap"))] + Conditional(HashMap), +} + +impl Default for DependsOnOptions { + fn default() -> Self { + Self::Simple(Vec::new()) + } +} + +impl DependsOnOptions { + pub fn is_empty(&self) -> bool { + match self { + Self::Simple(v) => v.is_empty(), + Self::Conditional(m) => m.is_empty(), + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)] +pub struct DependsCondition { + pub condition: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct LoggingParameters { + pub driver: String, + #[cfg(feature = "indexmap")] + #[serde(skip_serializing_if = "Option::is_none")] + pub options: Option>, + #[cfg(not(feature = "indexmap"))] + #[serde(skip_serializing_if = "Option::is_none")] + pub options: Option>, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum Ports { + Short(Vec), + Long(Vec), +} + +impl Default for Ports { + fn default() -> Self { + Self::Short(Vec::default()) + } +} + +impl Ports { + pub fn is_empty(&self) -> bool { + match self { + Self::Short(v) => v.is_empty(), + Self::Long(v) => v.is_empty(), + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct Port { + pub target: u16, + #[serde(skip_serializing_if = "Option::is_none")] + pub host_ip: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub published: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub protocol: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub mode: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum PublishedPort { + Single(u16), + Range(String), +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum Environment { + List(Vec), + #[cfg(feature = "indexmap")] + KvPair(IndexMap>), + #[cfg(not(feature = "indexmap"))] + KvPair(HashMap>), +} + +impl Default for Environment { + fn default() -> Self { + Self::List(Vec::new()) + } +} + +impl Environment { + pub fn is_empty(&self) -> bool { + match self { + Self::List(v) => v.is_empty(), + Self::KvPair(m) => m.is_empty(), + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default, Ord, PartialOrd)] +#[serde(try_from = "String")] +pub struct Extension(String); + +impl FromStr for Extension { + type Err = ExtensionParseError; + + fn from_str(s: &str) -> Result { + let owned = s.to_owned(); + Extension::try_from(owned) + } +} + +impl TryFrom for Extension { + type Error = ExtensionParseError; + + fn try_from(s: String) -> Result { + if s.starts_with("x-") { + Ok(Self(s)) + } else { + Err(ExtensionParseError(s)) + } + } +} + +/// The result of a failed TryFrom conversion for [`Extension`] +/// +/// Contains the string that was being converted +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct ExtensionParseError(pub String); + +impl fmt::Display for ExtensionParseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "unknown attribute {:?}, extensions must start with 'x-' (see https://docs.docker.com/compose/compose-file/#extension)", self.0) + } +} + +impl std::error::Error for ExtensionParseError {} + +#[cfg(feature = "indexmap")] +#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct Services(pub IndexMap>); +#[cfg(not(feature = "indexmap"))] +#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct Services(pub HashMap>); + +impl Services { + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] +#[serde(untagged)] +pub enum Labels { + List(Vec), + #[cfg(feature = "indexmap")] + Map(IndexMap), + #[cfg(not(feature = "indexmap"))] + Map(HashMap), +} + +impl Default for Labels { + fn default() -> Self { + Self::List(Vec::new()) + } +} + +impl Labels { + pub fn is_empty(&self) -> bool { + match self { + Self::List(v) => v.is_empty(), + Self::Map(m) => m.is_empty(), + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] +#[serde(untagged)] +pub enum Tmpfs { + Simple(String), + List(Vec), +} + +#[cfg(feature = "indexmap")] +#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)] +pub struct Ulimits(pub IndexMap); +#[cfg(not(feature = "indexmap"))] +#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)] +pub struct Ulimits(pub HashMap); + +impl Ulimits { + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] +#[serde(untagged)] +pub enum Ulimit { + Single(i64), + SoftHard { soft: i64, hard: i64 }, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] +#[serde(untagged)] +pub enum Networks { + Simple(Vec), + Advanced(AdvancedNetworks), +} + +impl Default for Networks { + fn default() -> Self { + Self::Simple(Vec::new()) + } +} + +impl Networks { + pub fn is_empty(&self) -> bool { + match self { + Self::Simple(n) => n.is_empty(), + Self::Advanced(n) => n.0.is_empty(), + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] +#[serde(untagged)] +pub enum BuildStep { + Simple(String), + Advanced(AdvancedBuildStep), +} + +#[derive(Builder, Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Default)] +#[serde(deny_unknown_fields)] +#[builder(setter(into), default)] +pub struct AdvancedBuildStep { + pub context: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub dockerfile: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub shm_size: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub target: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub network: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub cache_from: Vec, + #[serde(default, skip_serializing_if = "Labels::is_empty")] + pub labels: Labels, +} + +#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] +#[serde(untagged)] +pub enum BuildArgs { + Simple(String), + List(Vec), + #[cfg(feature = "indexmap")] + KvPair(IndexMap), + #[cfg(not(feature = "indexmap"))] + KvPair(HashMap), +} + +#[cfg(feature = "indexmap")] +#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)] +pub struct AdvancedNetworks(pub IndexMap>); +#[cfg(not(feature = "indexmap"))] +#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)] +pub struct AdvancedNetworks(pub HashMap>); + +#[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, + #[serde(skip_serializing_if = "Option::is_none")] + pub ipv6_address: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub aliases: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum SysCtls { + List(Vec), + #[cfg(feature = "indexmap")] + Map(IndexMap>), + #[cfg(not(feature = "indexmap"))] + Map(HashMap>), +} + +impl Default for SysCtls { + fn default() -> Self { + Self::List(Vec::new()) + } +} + +impl SysCtls { + pub fn is_empty(&self) -> bool { + match self { + Self::List(v) => v.is_empty(), + Self::Map(m) => m.is_empty(), + } + } +} + +#[cfg(feature = "indexmap")] +#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct TopLevelVolumes(pub IndexMap>); +#[cfg(not(feature = "indexmap"))] +#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct TopLevelVolumes(pub HashMap>); + +impl TopLevelVolumes { + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct ComposeVolume { + #[serde(skip_serializing_if = "Option::is_none")] + pub driver: Option, + #[cfg(feature = "indexmap")] + #[serde(default, skip_serializing_if = "IndexMap::is_empty")] + pub driver_opts: IndexMap>, + #[cfg(not(feature = "indexmap"))] + #[serde(default, skip_serializing_if = "HashMap::is_empty")] + pub driver_opts: HashMap>, + #[serde(skip_serializing_if = "Option::is_none")] + pub external: Option, + #[serde(default, skip_serializing_if = "Labels::is_empty")] + pub labels: Labels, + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum ExternalVolume { + Bool(bool), + Name { name: String }, +} + +#[cfg(feature = "indexmap")] +#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct ComposeNetworks(pub IndexMap>); + +#[cfg(not(feature = "indexmap"))] +#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct ComposeNetworks(pub HashMap>); + +impl ComposeNetworks { + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)] +#[serde(untagged)] +pub enum ComposeNetwork { + Detailed(ComposeNetworkSettingDetails), + Bool(bool), +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)] +#[serde(deny_unknown_fields)] +pub struct ComposeNetworkSettingDetails { + pub name: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)] +#[serde(deny_unknown_fields)] +pub struct ExternalNetworkSettingBool(bool); + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)] +#[serde(deny_unknown_fields)] +pub struct NetworkSettings { + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub attachable: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub driver: Option, + #[cfg(feature = "indexmap")] + #[serde(default, skip_serializing_if = "IndexMap::is_empty")] + pub driver_opts: IndexMap>, + #[cfg(not(feature = "indexmap"))] + #[serde(default, skip_serializing_if = "HashMap::is_empty")] + pub driver_opts: HashMap>, + #[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, + #[serde(skip_serializing_if = "Option::is_none")] + pub ipam: Option, + #[serde(default, skip_serializing_if = "Labels::is_empty")] + pub labels: Labels, + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, +} + +#[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, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub config: Vec, +} + +#[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, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)] +#[serde(deny_unknown_fields)] +pub struct Deploy { + #[serde(skip_serializing_if = "Option::is_none")] + pub mode: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub replicas: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub labels: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub update_config: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub resources: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub restart_policy: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub placement: Option, +} + +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, + #[serde(skip_serializing_if = "Option::is_none")] + pub interval: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub timeout: Option, + #[serde(default, skip_serializing_if = "is_zero")] + pub retries: i64, + #[serde(skip_serializing_if = "Option::is_none")] + pub start_period: Option, + #[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), +} + +#[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, + #[serde(skip_serializing_if = "Option::is_none")] + pub memory: Option, +} + +#[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, + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub preferences: Vec, +} + +#[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, + pub reservations: Option, +} + +#[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, + #[serde(skip_serializing_if = "Option::is_none")] + pub delay: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub max_attempts: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub window: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)] +#[serde(deny_unknown_fields)] +pub struct UpdateConfig { + #[serde(skip_serializing_if = "Option::is_none")] + pub parallelism: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub delay: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub failure_action: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub monitor: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub max_failure_ratio: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)] +#[serde(untagged)] +pub enum Volumes { + Simple(Vec), + Advanced(Vec), +} + +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, + 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, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub volume: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tmpfs: Option, +} + +#[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), +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)] +#[serde(untagged)] +pub enum Entrypoint { + Simple(String), + List(Vec), +} + +#[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 { + Map(T), + Empty, +} + +impl Default for MapOrEmpty { + fn default() -> Self { + Self::Empty + } +} + +impl From> for Option { + fn from(value: MapOrEmpty) -> Self { + match value { + MapOrEmpty::Map(t) => Some(t), + MapOrEmpty::Empty => None, + } + } +} + +impl Serialize for MapOrEmpty + where + T: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + Self::Map(t) => t.serialize(serializer), + Self::Empty => { + use serde::ser::SerializeMap; + serializer.serialize_map(None)?.end() + } + } + } +} \ No newline at end of file diff --git a/src/helpers/stack/mod.rs b/src/helpers/stack/mod.rs new file mode 100644 index 0000000..7da2d2e --- /dev/null +++ b/src/helpers/stack/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod builder; +pub(crate) mod dctypes; \ No newline at end of file diff --git a/src/models/stack.rs b/src/models/stack.rs index 21432d3..9ce7370 100644 --- a/src/models/stack.rs +++ b/src/models/stack.rs @@ -3,7 +3,7 @@ use serde_json::Value; use uuid::Uuid; use serde::{Serialize,Deserialize}; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct Stack { pub id: i32, // id - is a unique identifier for the app stack pub stack_id: Uuid, // external stack ID diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs index d1d8b87..aa1cfce 100644 --- a/src/routes/stack/add.rs +++ b/src/routes/stack/add.rs @@ -15,8 +15,7 @@ use sqlx::PgPool; use std::str; use tracing::Instrument; use uuid::Uuid; - - +use crate::helpers::stack::builder::DcBuilder; #[tracing::instrument(name = "Add stack.")] #[post("")] @@ -110,3 +109,52 @@ pub async fn add( } }; } + +#[tracing::instrument(name = "Generate docker-compose.")] +#[post("/{id}")] +pub async fn gen( + user: web::ReqData, + path: web::Path<(i32,)>, + pool: Data, +) -> Result { + let id = path.0; + tracing::debug!("Received id: {}", id); + + let stack = match sqlx::query_as!( + Stack, + r#" + SELECT * FROM user_stack WHERE id=$1 AND user_id=$2 LIMIT 1 + "#, + id, user.id + ) + .fetch_one(pool.get_ref()) + .await + { + Ok(stack) => { + tracing::info!("stack found: {:?}", stack.id,); + Some(stack) + } + Err(sqlx::Error::RowNotFound) => { + tracing::error!("Row not found 404"); + None + } + Err(e) => { + tracing::error!("Failed to fetch stack, error: {:?}", e); + None + } + }; + + + match stack { + Some(stack) => { + let mut dc = DcBuilder::new(); + let fc = dc.build(stack); + tracing::debug!("Docker compose file content {:?}", fc.unwrap()); + } + None => { + + } + } + + Ok(Json(JsonResponse::::ok(1, "OK"))) +} diff --git a/src/routes/stack/get.rs b/src/routes/stack/get.rs index 7b5fa7f..b3ce389 100644 --- a/src/routes/stack/get.rs +++ b/src/routes/stack/get.rs @@ -46,3 +46,4 @@ pub async fn get( } } } + diff --git a/src/startup.rs b/src/startup.rs index edd58c1..773ce2c 100644 --- a/src/startup.rs +++ b/src/startup.rs @@ -128,6 +128,7 @@ pub async fn run( .wrap(HttpAuthentication::bearer(bearer_guard)) .wrap(Cors::permissive()) .service(crate::routes::stack::add::add) + .service(crate::routes::stack::add::gen) .service(crate::routes::stack::get::get) ) .app_data(db_pool.clone()) From d04ecab475ff60907c257246aa4fed29d9213029 Mon Sep 17 00:00:00 2001 From: vsilent Date: Tue, 7 Nov 2023 16:23:09 +0200 Subject: [PATCH 04/18] return contents of the compose file on build --- files/{docker-compose.yml => 1.yml} | 0 .../c60d329f-9159-48b7-abf3-10ccabbca213.yml | 20 ++++++ src/helpers/stack/builder.rs | 65 +++++++------------ src/routes/stack/add.rs | 21 ++++-- 4 files changed, 59 insertions(+), 47 deletions(-) rename files/{docker-compose.yml => 1.yml} (100%) create mode 100644 files/c60d329f-9159-48b7-abf3-10ccabbca213.yml diff --git a/files/docker-compose.yml b/files/1.yml similarity index 100% rename from files/docker-compose.yml rename to files/1.yml diff --git a/files/c60d329f-9159-48b7-abf3-10ccabbca213.yml b/files/c60d329f-9159-48b7-abf3-10ccabbca213.yml new file mode 100644 index 0000000..c6e60d6 --- /dev/null +++ b/files/c60d329f-9159-48b7-abf3-10ccabbca213.yml @@ -0,0 +1,20 @@ +version: '3.8' +services: + smarty-bot: + image: trydirect/smarty-bot:latest + ports: + - target: 8000 + published: 8000 + restart: always + pgrst: + image: pgrst + ports: + - target: 9999 + published: 9999 + restart: always + portainer_ce_feature: + image: portainer-ce-feature + ports: + - target: 9000 + published: 9000 + restart: always diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs index 023ee96..1ee5fe9 100644 --- a/src/helpers/stack/builder.rs +++ b/src/helpers/stack/builder.rs @@ -1,7 +1,18 @@ -use crate::helpers::stack::dctypes::{Compose, Port, Ports, PublishedPort, Service, Services, SingleService}; +use crate::helpers::stack::dctypes::{ + Compose, + Port, + Ports, + PublishedPort, + Service, + Services +}; use serde_yaml; use crate::forms; -use crate::forms::{StackForm, Web, Feature}; +use crate::forms::{ + StackForm, + Web, + Feature +}; use crate::models::stack::Stack; #[derive(Clone, Debug)] @@ -60,44 +71,17 @@ fn convert_shared_ports(ports: Vec) -> Result, String> { impl DcBuilder { - pub fn new() -> Self { + pub fn new(stack: Stack) -> Self { DcBuilder { config: Config::default(), - stack: Stack { - id: 0, - stack_id: Default::default(), - user_id: "".to_string(), - name: "".to_string(), - body: Default::default(), - created_at: Default::default(), - updated_at: Default::default(), - }, + stack: stack, } } - // pub fn add_ports(&self, ports: &Vec) -> Vec { - // // @todo re-factor using TryInto or TryFrom - // - // let mut _ports:Vec = vec![]; - // for p in ports { - // let port = p.parse::().unwrap(); - // _ports.push( - // Port { - // target: port, - // host_ip: None, - // published: Some(PublishedPort::Single(port)), - // protocol: None, - // mode: None, - // } - // ); - // } - // _ports - // } - - pub fn build(&self, stack:Stack) -> Option { - - tracing::debug!("Start build docker compose from {:?}", stack.body); - let _stack = serde_json::from_value::(stack.body); + pub fn build(&self) -> Option { + + tracing::debug!("Start build docker compose from {:?}", &self.stack.body); + let _stack = serde_json::from_value::(self.stack.body.clone()); let mut services = indexmap::IndexMap::new(); match _stack { Ok(apps) => { @@ -115,7 +99,6 @@ impl DcBuilder { if let Some(ports) = &app.shared_ports { if !ports.is_empty() { - // service.ports = Ports::Long(self.add_ports(ports)); service.ports = Ports::Long(app.try_into().unwrap()) } } @@ -142,7 +125,6 @@ impl DcBuilder { if let Some(ports) = &app.shared_ports { if !ports.is_empty() { - // service.ports = Ports::Long(self.add_ports(ports)); service.ports = Ports::Long(app.try_into().unwrap()) } } @@ -167,7 +149,6 @@ impl DcBuilder { if let Some(ports) = &app.shared_ports { if !ports.is_empty() { - // service.ports = Ports::Long(self.add_ports(ports)); service.ports = Ports::Long(app.try_into().unwrap()) } } @@ -193,15 +174,17 @@ impl DcBuilder { ..Default::default() }; - let target_file = std::path::Path::new("./files/docker-compose.yml"); + let fname= format!("./files/{}.yml", self.stack.stack_id); + tracing::debug!("Save docker compose to file {:?}", fname); + let target_file = std::path::Path::new(fname.as_str()); // serialize to string let serialized = match serde_yaml::to_string(&compose_content) { Ok(s) => s, Err(e) => panic!("Failed to serialize docker-compose file: {}", e), }; // serialize to file - std::fs::write(target_file, serialized).unwrap(); + std::fs::write(target_file, serialized.clone()).unwrap(); - Some(compose_content) + Some(serialized) } } diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs index aa1cfce..ef3e0fc 100644 --- a/src/routes/stack/add.rs +++ b/src/routes/stack/add.rs @@ -16,6 +16,7 @@ use std::str; use tracing::Instrument; use uuid::Uuid; use crate::helpers::stack::builder::DcBuilder; +use crate::helpers::stack::dctypes::Compose; #[tracing::instrument(name = "Add stack.")] #[post("")] @@ -147,14 +148,22 @@ pub async fn gen( match stack { Some(stack) => { - let mut dc = DcBuilder::new(); - let fc = dc.build(stack); - tracing::debug!("Docker compose file content {:?}", fc.unwrap()); + let id = stack.id.clone(); + let mut dc = DcBuilder::new(stack); + let fc = dc.build(); + // tracing::debug!("Docker compose file content {:?}", fc.unwrap()); + return Ok(Json(JsonResponse::new( + "OK".to_owned(), + "Success".to_owned(), + 200, + Some(id), + Some(fc.unwrap()), + None + ))); + } None => { - + return Ok(Json(JsonResponse::internal_error("Could not generate compose file"))); } } - - Ok(Json(JsonResponse::::ok(1, "OK"))) } From 327bc563a1ab8fc2dee5451e57b7d4b7d9f396d5 Mon Sep 17 00:00:00 2001 From: vsilent Date: Tue, 7 Nov 2023 16:25:00 +0200 Subject: [PATCH 05/18] exclude ./files from git --- .gitignore | 1 + .idea/stacker.iml | 2 ++ files/1.yml | 20 ------------------- .../c60d329f-9159-48b7-abf3-10ccabbca213.yml | 20 ------------------- 4 files changed, 3 insertions(+), 40 deletions(-) delete mode 100644 files/1.yml delete mode 100644 files/c60d329f-9159-48b7-abf3-10ccabbca213.yml diff --git a/.gitignore b/.gitignore index c507849..3acd8af 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target .idea +files diff --git a/.idea/stacker.iml b/.idea/stacker.iml index 227e58a..a97e925 100644 --- a/.idea/stacker.iml +++ b/.idea/stacker.iml @@ -4,6 +4,8 @@ + + diff --git a/files/1.yml b/files/1.yml deleted file mode 100644 index c6e60d6..0000000 --- a/files/1.yml +++ /dev/null @@ -1,20 +0,0 @@ -version: '3.8' -services: - smarty-bot: - image: trydirect/smarty-bot:latest - ports: - - target: 8000 - published: 8000 - restart: always - pgrst: - image: pgrst - ports: - - target: 9999 - published: 9999 - restart: always - portainer_ce_feature: - image: portainer-ce-feature - ports: - - target: 9000 - published: 9000 - restart: always diff --git a/files/c60d329f-9159-48b7-abf3-10ccabbca213.yml b/files/c60d329f-9159-48b7-abf3-10ccabbca213.yml deleted file mode 100644 index c6e60d6..0000000 --- a/files/c60d329f-9159-48b7-abf3-10ccabbca213.yml +++ /dev/null @@ -1,20 +0,0 @@ -version: '3.8' -services: - smarty-bot: - image: trydirect/smarty-bot:latest - ports: - - target: 8000 - published: 8000 - restart: always - pgrst: - image: pgrst - ports: - - target: 9999 - published: 9999 - restart: always - portainer_ce_feature: - image: portainer-ce-feature - ports: - - target: 9000 - published: 9000 - restart: always From 9726e6d97c0a2304006769ebd283582857f0c81a Mon Sep 17 00:00:00 2001 From: vsilent Date: Sun, 12 Nov 2023 11:24:44 +0200 Subject: [PATCH 06/18] generate docker compose, response builder, dctypes --- Cargo.lock | 508 ++++++++++++++++++++++++++++++++++- Cargo.toml | 4 +- configuration.yaml | 7 + src/configuration.rs | 17 ++ src/forms/stack.rs | 36 ++- src/helpers/json.rs | 95 +++++-- src/helpers/stack/builder.rs | 4 +- src/models/stack.rs | 10 + src/routes/stack/add.rs | 65 +---- src/routes/stack/compose.rs | 71 +++++ src/routes/stack/deploy.rs | 124 ++++++++- src/routes/stack/get.rs | 28 +- src/routes/stack/mod.rs | 2 + src/startup.rs | 3 +- 14 files changed, 873 insertions(+), 101 deletions(-) create mode 100644 src/routes/stack/compose.rs diff --git a/Cargo.lock b/Cargo.lock index 68ef0eb..332a9ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -279,6 +279,54 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +[[package]] +name = "amq-protocol" +version = "7.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d40d8b2465c7959dd40cee32ba6ac334b5de57e9fca0cc756759894a4152a5d" +dependencies = [ + "amq-protocol-tcp", + "amq-protocol-types", + "amq-protocol-uri", + "cookie-factory", + "nom", + "serde", +] + +[[package]] +name = "amq-protocol-tcp" +version = "7.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cb2100adae7da61953a2c3a01935d86caae13329fadce3333f524d6d6ce12e2" +dependencies = [ + "amq-protocol-uri", + "tcp-stream", + "tracing", +] + +[[package]] +name = "amq-protocol-types" +version = "7.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "156ff13c8a3ced600b4e54ed826a2ae6242b6069d00dd98466827cef07d3daff" +dependencies = [ + "cookie-factory", + "nom", + "serde", + "serde_json", +] + +[[package]] +name = "amq-protocol-uri" +version = "7.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "751bbd7d440576066233e740576f1b31fdc6ab86cfabfbd48c548de77eca73e4" +dependencies = [ + "amq-protocol-types", + "percent-encoding", + "url", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -294,6 +342,104 @@ dependencies = [ "libc", ] +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b0c4a4f319e45986f347ee47fef8bf5e81c9abc3f6f58dc2391439f30df65f0" +dependencies = [ + "async-lock", + "async-task", + "concurrent-queue", + "fastrand 2.0.1", + "futures-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" +dependencies = [ + "async-channel", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + +[[package]] +name = "async-global-executor-trait" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33dd14c5a15affd2abcff50d84efd4009ada28a860f01c14f9d654f3e81b3f75" +dependencies = [ + "async-global-executor", + "async-trait", + "executor-trait", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite", + "log", + "parking", + "polling", + "rustix 0.37.27", + "slab", + "socket2 0.4.9", + "waker-fn", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-reactor-trait" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6012d170ad00de56c9ee354aef2e358359deb1ec504254e0e5a3774771de0e" +dependencies = [ + "async-io", + "async-trait", + "futures-core", + "reactor-trait", +] + +[[package]] +name = "async-task" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4eb2cdb97421e01129ccb49169d8279ed21e829929144f4a22a6e54ac549ca1" + [[package]] name = "async-trait" version = "0.1.74" @@ -314,6 +460,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.1.0" @@ -368,6 +520,31 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blocking" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c36a4d0d48574b3dd360b4b7d95cc651d2b6557b6402848a27d4b228a473e2a" +dependencies = [ + "async-channel", + "async-lock", + "async-task", + "fastrand 2.0.1", + "futures-io", + "futures-lite", + "piper", + "tracing", +] + [[package]] name = "brotli" version = "3.4.0" @@ -416,6 +593,15 @@ dependencies = [ "bytes", ] +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.0.83" @@ -448,6 +634,25 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "concurrent-queue" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "config" version = "0.13.3" @@ -484,6 +689,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "cookie-factory" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b" + [[package]] name = "core-foundation" version = "0.9.3" @@ -650,6 +861,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "des" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e" +dependencies = [ + "cipher", +] + [[package]] name = "digest" version = "0.10.7" @@ -687,6 +907,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "dotenvy" version = "0.15.7" @@ -733,6 +959,24 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "executor-trait" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a1052dd43212a7777ec6a69b117da52f5e52f07aec47d00c1a2b33b85d06b08" +dependencies = [ + "async-trait", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "fastrand" version = "2.0.1" @@ -755,6 +999,18 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "flume" +version = "0.10.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" +dependencies = [ + "futures-core", + "futures-sink", + "pin-project", + "spin 0.9.8", +] + [[package]] name = "fnv" version = "1.0.7" @@ -812,6 +1068,27 @@ dependencies = [ "parking_lot 0.11.2", ] +[[package]] +name = "futures-io" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + [[package]] name = "futures-sink" version = "0.3.28" @@ -1099,6 +1376,16 @@ dependencies = [ "serde", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", +] + [[package]] name = "instant" version = "0.1.12" @@ -1108,6 +1395,17 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + [[package]] name = "ipnet" version = "2.8.0" @@ -1173,6 +1471,29 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" +[[package]] +name = "lapin" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f3067a1fcfbc3fc46455809c023e69b8f6602463201010f4ae5a3b572adb9dc" +dependencies = [ + "amq-protocol", + "async-global-executor-trait", + "async-reactor-trait", + "async-trait", + "executor-trait", + "flume", + "futures-core", + "futures-io", + "parking_lot 0.12.1", + "pinky-swear", + "reactor-trait", + "serde", + "serde_json", + "tracing", + "waker-fn", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -1191,6 +1512,12 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + [[package]] name = "linux-raw-sys" version = "0.4.10" @@ -1420,6 +1747,29 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "p12" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4873306de53fe82e7e484df31e1e947d61514b6ea2ed6cd7b45d63006fd9224" +dependencies = [ + "cbc", + "cipher", + "des", + "getrandom", + "hmac", + "lazy_static", + "rc2", + "sha1", + "yasna", +] + +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + [[package]] name = "parking_lot" version = "0.11.2" @@ -1563,12 +1913,51 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pinky-swear" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d894b67aa7a4bf295db5e85349078c604edaa6fa5c8721e8eca3c7729a27f2ac" +dependencies = [ + "doc-comment", + "flume", + "parking_lot 0.12.1", + "tracing", +] + +[[package]] +name = "piper" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +dependencies = [ + "atomic-waker", + "fastrand 2.0.1", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1653,6 +2042,26 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rc2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62c64daa8e9438b84aaae55010a93f396f8e60e3911590fcba770d04643fc1dd" +dependencies = [ + "cipher", +] + +[[package]] +name = "reactor-trait" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "438a4293e4d097556730f4711998189416232f009c137389e0f961d2bc0ddc58" +dependencies = [ + "async-trait", + "futures-core", + "futures-io", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -1838,6 +2247,20 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.37.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys", +] + [[package]] name = "rustix" version = "0.38.19" @@ -1847,7 +2270,7 @@ dependencies = [ "bitflags 2.4.1", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.10", "windows-sys", ] @@ -1863,6 +2286,42 @@ dependencies = [ "webpki", ] +[[package]] +name = "rustls" +version = "0.21.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c" +dependencies = [ + "log", + "ring 0.17.4", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-connector" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "060bcc1795b840d0e56d78f3293be5f652aa1611d249b0e63ffe19f4a8c9ae23" +dependencies = [ + "log", + "rustls 0.21.8", + "rustls-native-certs", + "rustls-webpki", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "1.0.3" @@ -1872,6 +2331,16 @@ dependencies = [ "base64 0.21.4", ] +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring 0.17.4", + "untrusted 0.9.0", +] + [[package]] name = "ryu" version = "1.0.15" @@ -2118,6 +2587,9 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] [[package]] name = "sqlformat" @@ -2177,7 +2649,7 @@ dependencies = [ "paste", "percent-encoding", "rand", - "rustls", + "rustls 0.20.9", "rustls-pemfile", "serde", "serde_json", @@ -2238,8 +2710,10 @@ dependencies = [ "chrono", "config", "derive_builder", + "futures-lite", "glob", "indexmap 2.1.0", + "lapin", "rand", "regex", "reqwest", @@ -2325,6 +2799,18 @@ dependencies = [ "libc", ] +[[package]] +name = "tcp-stream" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4da30af7998f51ee1aa48ab24276fe303a697b004e31ff542b192c088d5630a5" +dependencies = [ + "cfg-if", + "p12", + "rustls-connector", + "rustls-pemfile", +] + [[package]] name = "tempfile" version = "3.8.0" @@ -2332,9 +2818,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ "cfg-if", - "fastrand", + "fastrand 2.0.1", "redox_syscall 0.3.5", - "rustix", + "rustix 0.38.19", "windows-sys", ] @@ -2469,7 +2955,7 @@ version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "rustls", + "rustls 0.20.9", "tokio", "webpki", ] @@ -2714,6 +3200,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "waker-fn" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" + [[package]] name = "want" version = "0.3.1" @@ -2956,6 +3448,12 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" + [[package]] name = "zstd" version = "0.12.4" diff --git a/Cargo.toml b/Cargo.toml index 581f4d5..4112688 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,8 @@ rand = "0.8.5" 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" [dependencies.sqlx] version = "0.6.3" @@ -56,4 +58,4 @@ default = ["indexmap"] indexmap = ["dep:indexmap"] [dev-dependencies] -glob = "0.3" \ No newline at end of file +glob = "0.3" diff --git a/configuration.yaml b/configuration.yaml index 1de0778..4f88a4c 100644 --- a/configuration.yaml +++ b/configuration.yaml @@ -9,3 +9,10 @@ database: username: postgres password: postgres database_name: stacker + +amqp: + host: 51.15.74.139 + port: 5672 + username: guest + password: wua4Eeyi + diff --git a/src/configuration.rs b/src/configuration.rs index a3beeaf..90d22c9 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -7,6 +7,7 @@ pub struct Settings { pub app_host: String, pub auth_url: String, pub max_clients_number: i64, + pub amqp: AmqpSettings } #[derive(Debug, serde::Deserialize)] @@ -18,6 +19,13 @@ pub struct DatabaseSettings { pub database_name: String, } +#[derive(Debug, serde::Deserialize)] +pub struct AmqpSettings { + pub username: String, + pub password: String, + pub host: String, + pub port: u16, +} impl DatabaseSettings { // Connection string: postgresql://:@:/ pub fn connection_string(&self) -> String { @@ -35,6 +43,15 @@ impl DatabaseSettings { } } +impl AmqpSettings { + pub fn connection_string(&self) -> String { + format!( + "amqp://{}:{}@{}:{}/%2f", + self.username, self.password, self.host, self.port, + ) + } +} + pub fn get_configuration() -> Result { // Initialize our configuration reader let mut settings = config::Config::default(); diff --git a/src/forms/stack.rs b/src/forms/stack.rs index 93a1be8..ce9105e 100644 --- a/src/forms/stack.rs +++ b/src/forms/stack.rs @@ -3,8 +3,42 @@ use serde_json::Value; use serde_valid::Validate; #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)] -#[serde(rename_all = "camelCase")] +#[serde(rename_all = "snake_case")] pub struct StackForm { + #[serde(rename= "commonDomain")] + pub common_domain: String, + pub domain_list: Option, + pub region: String, + pub zone: Option, + pub server: String, + pub os: String, + pub ssl: String, + pub vars: Option>, + #[serde(rename = "integrated_features")] + pub integrated_features: Option>, + #[serde(rename = "extended_features")] + pub extended_features: Option>, + pub subscriptions: Option>, + #[serde(rename = "save_token")] + pub save_token: bool, + #[serde(rename = "cloud_token")] + pub cloud_token: String, + pub provider: String, + #[serde(rename = "stack_code")] + pub stack_code: String, + #[serde(rename = "selected_plan")] + pub selected_plan: String, + pub custom: Custom, +} + + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)] +#[serde(rename_all = "snake_case")] +pub struct StackPayload { + pub(crate) user_token: Option, + pub(crate) user_email: Option, + pub(crate) installation_id: Option, + #[serde(rename= "commonDomain")] pub common_domain: String, pub domain_list: Option, pub region: String, diff --git a/src/helpers/json.rs b/src/helpers/json.rs index ad18305..de5dfd9 100644 --- a/src/helpers/json.rs +++ b/src/helpers/json.rs @@ -1,8 +1,8 @@ -use actix_web::{HttpRequest, HttpResponse, Responder}; +use actix_web::{Responder, Result}; use serde_derive::Serialize; -use crate::models; +use actix_web::web; -#[derive(Serialize, Default)] +#[derive(Serialize)] pub(crate) struct JsonResponse { pub(crate) status: String, pub(crate) message: String, @@ -23,7 +23,67 @@ pub(crate) struct JsonResponse { // pub(crate) list: Option> // } -impl JsonResponse { +#[derive(Serialize, Default)] +pub struct JsonResponseBuilder + where T: serde::Serialize + Default +{ + status: String, + message: String, + code: u32, + id: Option, + item: Option, + list: Option> +} + +impl JsonResponseBuilder +where T: serde::Serialize + Default +{ + fn new() -> Self { + Self::default() + } + + fn set_item(mut self, item:T) -> Self { + self.item = Some(item); + self + } + + + fn set_list(mut self, list:Vec) -> Self { + self.list = Some(list); + self + } + + pub(crate) fn ok(self) -> Result { + + Ok(web::Json( + JsonResponse { + status: self.status, + message: self.message, + code: self.code, + id: self.id, + item: self.item, + list: self.list, + } + )) + } +} + +impl From for JsonResponseBuilder + where T: serde::Serialize + Default { + fn from(value: T) -> Self { + JsonResponseBuilder::default().set_item(value) + } +} + +impl From> for JsonResponseBuilder +where T: serde::Serialize + Default { + fn from(value: Vec) -> Self { + JsonResponseBuilder::default().set_list(value) + } +} + +impl JsonResponse +{ pub(crate) fn new(status: String, message: String, code: u32, @@ -55,8 +115,7 @@ impl JsonResponse { message: msg, code: 200, id: Some(id), - item: None, - list: None + ..Default::default() } } @@ -65,9 +124,7 @@ impl JsonResponse { status: "Error".to_string(), code: 404, message: format!("Object not found"), - id: None, - item: None, - list: None + ..Default::default() } } @@ -83,9 +140,7 @@ impl JsonResponse { status: "Error".to_string(), code: 500, message: msg, - id: None, - item: None, - list: None + ..Default::default() } } @@ -101,9 +156,19 @@ impl JsonResponse { status: "Error".to_string(), code: 400, message: msg, - id: None, - item: None, - list: None + ..Default::default() + } + } +} + +impl Default for JsonResponse { + + fn default() -> Self { + JsonResponse { + + status: "200".to_string(), + message: "OK".to_string(), + ..Default::default() } } } diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs index 1ee5fe9..a6c9fd9 100644 --- a/src/helpers/stack/builder.rs +++ b/src/helpers/stack/builder.rs @@ -30,7 +30,7 @@ impl Default for Config { #[derive(Clone, Debug)] pub struct DcBuilder { config: Config, - stack: Stack + pub(crate) stack: Stack } impl TryInto> for Web { @@ -88,10 +88,10 @@ impl DcBuilder { println!("stack item {:?}", apps.custom.web); for app in apps.custom.web { - // println!("app name {:?}", app.name); let tag = "latest"; let img= format!("{}/{}:{}",app.dockerhub_user, app.dockerhub_name, tag); let code = app.code.clone().to_owned(); + let mut service = Service { image: Some(img.to_string()), ..Default::default() diff --git a/src/models/stack.rs b/src/models/stack.rs index 9ce7370..f9ce272 100644 --- a/src/models/stack.rs +++ b/src/models/stack.rs @@ -14,3 +14,13 @@ pub struct Stack { pub created_at: DateTime, pub updated_at: DateTime, } + +impl Default for Stack { + fn default() -> Self { + Stack { + user_id: "".to_string(), + name: "".to_string(), + ..Default::default() + } + } +} \ No newline at end of file diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs index ef3e0fc..64c65cb 100644 --- a/src/routes/stack/add.rs +++ b/src/routes/stack/add.rs @@ -15,8 +15,7 @@ use sqlx::PgPool; use std::str; use tracing::Instrument; use uuid::Uuid; -use crate::helpers::stack::builder::DcBuilder; -use crate::helpers::stack::dctypes::Compose; + #[tracing::instrument(name = "Add stack.")] #[post("")] @@ -28,14 +27,12 @@ pub async fn add( let body_bytes = actix_web::body::to_bytes(body).await.unwrap(); let body_str = str::from_utf8(&body_bytes).unwrap(); - // method 1 let app_state: AppState = serde_json::from_str(body_str).unwrap(); - // method 2 let app_state = serde_json::from_str::(body_str).unwrap(); let form = match serde_json::from_str::(body_str) { Ok(f) => { f } - Err(err) => { - return Ok(Json(JsonResponse::::not_valid(""))); + Err(_err) => { + return Ok(Json(JsonResponse::::not_valid("Invalid data"))); } }; @@ -111,59 +108,3 @@ pub async fn add( }; } -#[tracing::instrument(name = "Generate docker-compose.")] -#[post("/{id}")] -pub async fn gen( - user: web::ReqData, - path: web::Path<(i32,)>, - pool: Data, -) -> Result { - let id = path.0; - tracing::debug!("Received id: {}", id); - - let stack = match sqlx::query_as!( - Stack, - r#" - SELECT * FROM user_stack WHERE id=$1 AND user_id=$2 LIMIT 1 - "#, - id, user.id - ) - .fetch_one(pool.get_ref()) - .await - { - Ok(stack) => { - tracing::info!("stack found: {:?}", stack.id,); - Some(stack) - } - Err(sqlx::Error::RowNotFound) => { - tracing::error!("Row not found 404"); - None - } - Err(e) => { - tracing::error!("Failed to fetch stack, error: {:?}", e); - None - } - }; - - - match stack { - Some(stack) => { - let id = stack.id.clone(); - let mut dc = DcBuilder::new(stack); - let fc = dc.build(); - // tracing::debug!("Docker compose file content {:?}", fc.unwrap()); - return Ok(Json(JsonResponse::new( - "OK".to_owned(), - "Success".to_owned(), - 200, - Some(id), - Some(fc.unwrap()), - None - ))); - - } - None => { - return Ok(Json(JsonResponse::internal_error("Could not generate compose file"))); - } - } -} diff --git a/src/routes/stack/compose.rs b/src/routes/stack/compose.rs new file mode 100644 index 0000000..dc3230d --- /dev/null +++ b/src/routes/stack/compose.rs @@ -0,0 +1,71 @@ +use actix_web::{ + web, + web::{Data, Json}, + Responder, Result, +}; + +use crate::helpers::JsonResponse; +use crate::models::user::User; +use crate::models::Stack; +use actix_web::post; +use sqlx::PgPool; +use std::str; +use tracing::Instrument; +use crate::helpers::stack::builder::DcBuilder; + +#[tracing::instrument(name = "Generate docker-compose.")] +#[post("/{id}")] +pub async fn add( + user: web::ReqData, + path: web::Path<(i32,)>, + pool: Data, +) -> Result { + let id = path.0; + tracing::debug!("Received id: {}", id); + + let stack = match sqlx::query_as!( + Stack, + r#" + SELECT * FROM user_stack WHERE id=$1 AND user_id=$2 LIMIT 1 + "#, + id, user.id + ) + .fetch_one(pool.get_ref()) + .await + { + Ok(stack) => { + tracing::info!("stack found: {:?}", stack.id,); + Some(stack) + } + Err(sqlx::Error::RowNotFound) => { + tracing::error!("Row not found 404"); + None + } + Err(e) => { + tracing::error!("Failed to fetch stack, error: {:?}", e); + None + } + }; + + + match stack { + Some(stack) => { + let id = stack.id.clone(); + let mut dc = DcBuilder::new(stack); + let fc = dc.build(); + // tracing::debug!("Docker compose file content {:?}", fc.unwrap()); + return Ok(Json(JsonResponse::new( + "OK".to_owned(), + "Success".to_owned(), + 200, + Some(id), + Some(fc.unwrap()), + None + ))); + + } + None => { + return Ok(Json(JsonResponse::internal_error("Could not generate compose file"))); + } + } +} diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs index 67edaa8..1c871e5 100644 --- a/src/routes/stack/deploy.rs +++ b/src/routes/stack/deploy.rs @@ -1,5 +1,121 @@ -use actix_web::HttpResponse; +use std::sync::Arc; +use actix_web::{ + web, + post, + web::{Data, Json}, + Responder, Result, +}; +use crate::models::user::User; +use crate::models::stack::Stack; +use sqlx::PgPool; +use lapin::{ + options::*, publisher_confirm::Confirmation, types::FieldTable, BasicProperties, Connection, + ConnectionProperties +}; +use crate::configuration::Settings; +use crate::helpers::JsonResponse; +use crate::helpers::stack::builder::DcBuilder; +use futures_lite::stream::StreamExt; +use serde::Serialize; +use crate::forms::{StackForm, StackPayload}; -pub async fn deploy() -> HttpResponse { - unimplemented!() -} \ No newline at end of file + +#[derive(Serialize, Debug, Clone)] +struct Payload { + user_token: String, + user_email: String, + installation_id: String, +} + + +#[tracing::instrument(name = "Deploy.")] +#[post("/{id}/deploy")] +pub async fn add( + user: web::ReqData, + path: web::Path<(i32,)>, + pool: Data, + sets: Data>, +) -> Result { + let id = path.0; + tracing::debug!("Received id: {}", id); + + let stack = match sqlx::query_as!( + Stack, + r#" + SELECT * FROM user_stack WHERE id=$1 AND user_id=$2 LIMIT 1 + "#, + id, user.id + ) + .fetch_one(pool.get_ref()) + .await + { + Ok(stack) => { + tracing::info!("Stack found: {:?}", stack.id,); + Some(stack) + } + Err(sqlx::Error::RowNotFound) => { + tracing::error!("Row not found 404"); + None + } + Err(e) => { + tracing::error!("Failed to fetch stack, error: {:?}", e); + None + } + }; + + return match stack { + Some(stack) => { + let id = stack.id.clone(); + let mut dc = DcBuilder::new(stack); + dc.build(); + + let addr = sets.amqp.connection_string(); + let routing_key = "install.start.tfa.all.all".to_string(); + tracing::debug!("Sending message to {:?}", routing_key); + + let conn = Connection::connect(&addr, ConnectionProperties::default()) + .await + .unwrap(); + + tracing::info!("RABBITMQ CONNECTED"); + + let channel = conn.create_channel().await.unwrap(); + let mut stack_data = serde_json::from_value::( + dc.stack.body.clone() + ).unwrap(); + + stack_data.installation_id = Some(1); + stack_data.user_token = Some(user.id.clone()); + stack_data.user_email= Some(user.email.clone()); + + let payload = serde_json::to_string::(&stack_data).unwrap(); + let _payload = payload.as_bytes(); + + let confirm = channel + .basic_publish( + "install", + routing_key.as_str(), + BasicPublishOptions::default(), + _payload, + BasicProperties::default(), + ) + .await.unwrap() + .await.unwrap(); + + assert_eq!(confirm, Confirmation::NotRequested); + tracing::debug!("Message sent to rabbitmq"); + + Ok(Json(JsonResponse::::new( + "OK".to_owned(), + "Success".to_owned(), + 200, + Some(id), + None, + None + ))) + } + None => { + Ok(Json(JsonResponse::internal_error("Deployment failed"))) + } + } +} diff --git a/src/routes/stack/get.rs b/src/routes/stack/get.rs index b3ce389..786d905 100644 --- a/src/routes/stack/get.rs +++ b/src/routes/stack/get.rs @@ -1,8 +1,9 @@ use actix_web::{web, get, Responder, Result}; use sqlx::PgPool; -use crate::helpers::JsonResponse; +use crate::helpers::{JsonResponse, JsonResponseBuilder}; use crate::models; use crate::models::user::User; +use std::convert::From; #[tracing::instrument(name = "Get logged user stack.")] @@ -29,20 +30,27 @@ pub async fn get( { Ok(stack) => { tracing::info!("stack found: {:?}", stack.id,); - return Ok(web::Json(JsonResponse::::new( - "Success".to_string(), - "".to_string(), - 200, - Some(stack.id), - Some(stack), - None))); + // return Ok(web::Json(JsonResponse::::new( + // "Success".to_string(), + // "".to_string(), + // 200, + // Some(stack.id), + // Some(stack), + // None))); + let response_builder:JsonResponseBuilder = From::from(stack); + // let response: JsonResponse = From::::from(stack).build(); + return response_builder.ok(); } Err(sqlx::Error::RowNotFound) => { - return Ok(web::Json(JsonResponse::::not_found())); + // return Ok(web::Json(JsonResponse::::not_found())); + let response_builder:JsonResponseBuilder =JsonResponseBuilder::default(); + return response_builder.ok(); } Err(e) => { tracing::error!("Failed to fetch stack, error: {:?}", e); - return Ok(web::Json(JsonResponse::::internal_error(""))); + // return Ok(web::Json(JsonResponse::::internal_error(""))); + let response_builder:JsonResponseBuilder = JsonResponseBuilder::default(); + return response_builder.ok(); } } } diff --git a/src/routes/stack/mod.rs b/src/routes/stack/mod.rs index f3e5bc9..27c8061 100644 --- a/src/routes/stack/mod.rs +++ b/src/routes/stack/mod.rs @@ -2,6 +2,8 @@ pub mod add; pub mod deploy; pub mod get; pub mod update; +pub(crate) mod compose; + pub use add::*; pub use update::*; pub use deploy::*; diff --git a/src/startup.rs b/src/startup.rs index 773ce2c..b7fd324 100644 --- a/src/startup.rs +++ b/src/startup.rs @@ -127,8 +127,9 @@ pub async fn run( web::scope("/stack") .wrap(HttpAuthentication::bearer(bearer_guard)) .wrap(Cors::permissive()) + .service(crate::routes::stack::deploy::add) .service(crate::routes::stack::add::add) - .service(crate::routes::stack::add::gen) + .service(crate::routes::stack::compose::add) .service(crate::routes::stack::get::get) ) .app_data(db_pool.clone()) From d5be426847f7299c38ad9611b29f0b003cc231d9 Mon Sep 17 00:00:00 2001 From: vsilent Date: Sun, 12 Nov 2023 11:25:51 +0200 Subject: [PATCH 07/18] rabbitmq config --- configuration.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configuration.yaml b/configuration.yaml index 4f88a4c..9498400 100644 --- a/configuration.yaml +++ b/configuration.yaml @@ -11,8 +11,8 @@ database: database_name: stacker amqp: - host: 51.15.74.139 + host: 127.0.0.1 port: 5672 username: guest - password: wua4Eeyi + password: From 49068d0974de880641961031160b3ca691954f1d Mon Sep 17 00:00:00 2001 From: vsilent Date: Mon, 13 Nov 2023 18:31:16 +0200 Subject: [PATCH 08/18] admin endpoints --- configuration.yaml | 6 +- custom-stack-payload-4.json | 2 +- custom-stack-payload-5.json | 1 + docker-compose.yml | 2 +- docker/dev/docker-compose.yml | 2 +- src/forms/stack.rs | 7 ++- src/helpers/json.rs | 110 +++++++++------------------------- src/routes/stack/compose.rs | 57 ++++++++++++++++++ src/routes/stack/deploy.rs | 8 +-- src/routes/stack/get.rs | 49 ++++++++++++++- src/startup.rs | 4 +- 11 files changed, 153 insertions(+), 95 deletions(-) create mode 100644 custom-stack-payload-5.json diff --git a/configuration.yaml b/configuration.yaml index 9498400..5a111da 100644 --- a/configuration.yaml +++ b/configuration.yaml @@ -1,6 +1,5 @@ app_host: 127.0.0.1 app_port: 8000 -#auth_url: https://65190108818c4e98ac6000e4.mockapi.io/user/1 auth_url: https://dev.try.direct/server/user/oauth_server/api/me max_clients_number: 2 database: @@ -11,8 +10,7 @@ database: database_name: stacker amqp: - host: 127.0.0.1 + host: 51.15.74.139 port: 5672 username: guest - password: - + password: guest diff --git a/custom-stack-payload-4.json b/custom-stack-payload-4.json index 07ee156..6f4bc7c 100644 --- a/custom-stack-payload-4.json +++ b/custom-stack-payload-4.json @@ -1 +1 @@ -{"commonDomain":"", "domainList":{}, "region":"fsn1", "zone":null, "server":"cx21", "os":"ubuntu-20.04", "ssl":"letsencrypt", "vars":[], "integrated_features":[],"extended_features":[],"subscriptions":["stack_migration"],"save_token":false,"cloud_token":"r6LAjqrynVt7pUwctVkzBlJmKjLOCxJIWjZFMLTkPYCCB4rsgphhEVhiL4DuO757","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"name":"Smarty Bot","code":"smarty-bot","domain":"smartybot.xyz","sharedPorts":["8000"],"versions":[],"custom":true,"type":"web","main":true,"_id":"lltkpq6p347kystct","dockerhub_user":"trydirect","dockerhub_name":"smarty-bot","url_app":"smartybot.xyz","url_git":"https://github.com/vsilent/smarty.git","disk_size":"1Gb","ram_size":"1Gb","cpu":1}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"

Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.6","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","versions":[{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}],"domain":"","sharedPorts":["9000"],"main":true,"version":{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}}],"service":[{"_etag":null,"_id":230,"_created":"2023-05-24T12:51:52.108972","_updated":"2023-08-04T12:18:34.670194","name":"pgrst","code":"pgrst","role":null,"type":"service","default":null,"popularity":null,"descr":null,"ports":null,"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":null,"category_id":null,"parent_app_id":null,"full_description":null,"description":"

PostgREST description

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"1","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"pgrst","versions":[{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"},{"_etag":null,"_id":563,"_created":null,"_updated":"2023-05-24T12:52:15.351522","app_id":230,"name":"0.0.5","version":"0.0.5","update_status":"ready_for_testing","tag":"0.0.5"}],"domain":"","sharedPorts":["9999"],"main":true,"version":{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"}}],"servers_count":3,"custom_stack_name":"mysampleproject2","custom_stack_code":"another-bot2","custom_stack_category":["New"],"custom_stack_short_description":"sample short description","custom_stack_description":"stack description","custom_stack_publish":false,"project_name":"Smarty Bot","project_git_url":"https://github.com/vsilent/smarty.git","project_overview":"my product 1","project_description":"my product 1"}} +{"commonDomain":"", "domainList":{}, "region":"fsn1", "zone":null, "server":"cx21", "os":"ubuntu-20.04", "ssl":"letsencrypt", "vars":[], "integrated_features":[],"extended_features":[],"subscriptions":["stack_migration"],"save_token":false,"cloud_token":"r6LAjqrynVt7pUwctVkzBlJmKjLOCxJIWjZFMLTkPYCCB4rsgphhEVhiL4DuO757","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"name":"Smarty Bot","code":"smarty-bot","domain":"smartybot.xyz","sharedPorts":["8000"],"versions":[],"custom":true,"type":"web","main":true,"_id":"lltkpq6p347kystct","dockerhub_user":"trydirect","dockerhub_name":"smarty-bot","url_app":"smartybot.xyz","url_git":"https://github.com/vsilent/smarty.git","disk_size":"1Gb","ram_size":"1Gb","cpu":1}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"

Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.6","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","versions":[{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}],"domain":"","sharedPorts":["9000"],"main":true,"version":{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}}],"service":[{"_etag":null,"_id":230,"_created":"2023-05-24T12:51:52.108972","_updated":"2023-08-04T12:18:34.670194","name":"pgrst","code":"pgrst","role":null,"type":"service","default":null,"popularity":null,"descr":null,"ports":null,"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":null,"category_id":null,"parent_app_id":null,"full_description":null,"description":"

PostgREST description

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"1","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"pgrst","versions":[{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"},{"_etag":null,"_id":563,"_created":null,"_updated":"2023-05-24T12:52:15.351522","app_id":230,"name":"0.0.5","version":"0.0.5","update_status":"ready_for_testing","tag":"0.0.5"}],"domain":"","sharedPorts":["9999"],"main":true,"version":{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"}}],"servers_count":3,"custom_stack_name":"mysampleproject4","custom_stack_code":"another-bot4","custom_stack_category":["New"],"custom_stack_short_description":"sample short description","custom_stack_description":"stack description","custom_stack_publish":false,"project_name":"Smarty Bot","project_git_url":"https://github.com/vsilent/smarty.git","project_overview":"my product 1","project_description":"my product 1"}} diff --git a/custom-stack-payload-5.json b/custom-stack-payload-5.json new file mode 100644 index 0000000..a8691db --- /dev/null +++ b/custom-stack-payload-5.json @@ -0,0 +1 @@ +{"commonDomain":"test.app", "domainList":{}, "region":"fsn1", "zone":null, "server":"cx21", "os":"ubuntu-20.04", "ssl":"letsencrypt", "vars":[], "integrated_features":[],"extended_features":[],"subscriptions":["stack_migration"],"save_token":false,"cloud_token":"r6LAjqrynVt7pUwctVkzBlJmKjLOCxJIWjZFMLTkPYCCB4rsgphhEVhiL4DuO757","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"name":"Smarty Bot","code":"smarty-bot","domain":"smartybot.xyz","sharedPorts":["8000"],"versions":[],"custom":true,"type":"web","main":true,"_id":"lltkpq6p347kystct","dockerhub_user":"trydirect","dockerhub_name":"smarty-bot","url_app":"smartybot.xyz","url_git":"https://github.com/vsilent/smarty.git","disk_size":"1Gb","ram_size":"1Gb","cpu":1}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"

Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.6","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","versions":[{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}],"domain":"","sharedPorts":["9000"],"main":true,"version":{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}}],"service":[{"_etag":null,"_id":230,"_created":"2023-05-24T12:51:52.108972","_updated":"2023-08-04T12:18:34.670194","name":"pgrst","code":"pgrst","role":null,"type":"service","default":null,"popularity":null,"descr":null,"ports":null,"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":null,"category_id":null,"parent_app_id":null,"full_description":null,"description":"

PostgREST description

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"1","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"pgrst","versions":[{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"},{"_etag":null,"_id":563,"_created":null,"_updated":"2023-05-24T12:52:15.351522","app_id":230,"name":"0.0.5","version":"0.0.5","update_status":"ready_for_testing","tag":"0.0.5"}],"domain":"","sharedPorts":["9999"],"main":true,"version":{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"}}],"servers_count":3,"custom_stack_name":"mysampleproject5","custom_stack_code":"another-bot5","custom_stack_category":["New"],"custom_stack_short_description":"sample short description","custom_stack_description":"stack description","custom_stack_publish":false,"project_name":"Smarty Bot","project_git_url":"https://github.com/vsilent/smarty.git","project_overview":"my product 1","project_description":"my product 1"}} diff --git a/docker-compose.yml b/docker-compose.yml index 2d3b934..26311c4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,7 +13,7 @@ networks: services: stacker: - image: trydirect/stacker:0.0.3 + image: trydirect/stacker:0.0.4 build: . container_name: stacker restart: always diff --git a/docker/dev/docker-compose.yml b/docker/dev/docker-compose.yml index 1ba68f2..bdfdd89 100644 --- a/docker/dev/docker-compose.yml +++ b/docker/dev/docker-compose.yml @@ -7,7 +7,7 @@ volumes: services: stacker: - image: trydirect/stacker:0.0.3 + image: trydirect/stacker:0.0.4 build: . container_name: stacker restart: always diff --git a/src/forms/stack.rs b/src/forms/stack.rs index ce9105e..0ee3ec6 100644 --- a/src/forms/stack.rs +++ b/src/forms/stack.rs @@ -88,7 +88,7 @@ pub struct Custom { #[serde(rename = "servers_count")] pub servers_count: i64, #[serde(rename = "custom_stack_name")] - pub custom_stack_name: String, + pub custom_stack_name: Option, #[serde(rename = "custom_stack_code")] pub custom_stack_code: String, #[serde(rename = "custom_stack_git_url")] @@ -114,6 +114,7 @@ pub struct Web { pub name: String, pub code: String, pub domain: Option, + #[serde(rename = "shared_ports")] pub shared_ports: Option>, pub versions: Option>, pub custom: bool, @@ -126,7 +127,9 @@ pub struct Web { pub dockerhub_user: String, #[serde(rename = "dockerhub_name")] pub dockerhub_name: String, + #[serde(rename = "url_app")] pub url_app: Option, + #[serde(rename = "url_git")] pub url_git: Option, #[validate(min_length=1)] #[validate(max_length=10)] @@ -196,6 +199,7 @@ pub struct Feature { pub dockerhub_image: Option, pub versions: Option>, pub domain: Option, + #[serde(rename = "shared_ports")] pub shared_ports: Option>, pub main: bool, } @@ -301,6 +305,7 @@ pub struct Service { pub dockerhub_image: Option, pub versions: Option>, pub domain: String, + #[serde(rename = "shared_ports")] pub shared_ports: Option>, pub main: bool, } diff --git a/src/helpers/json.rs b/src/helpers/json.rs index de5dfd9..6321a06 100644 --- a/src/helpers/json.rs +++ b/src/helpers/json.rs @@ -1,6 +1,7 @@ use actix_web::{Responder, Result}; use serde_derive::Serialize; use actix_web::web; +use actix_web::web::JsonBody::Error; #[derive(Serialize)] pub(crate) struct JsonResponse { @@ -12,16 +13,6 @@ pub(crate) struct JsonResponse { pub(crate) list: Option> } -// -// #[derive(Serialize)] -// pub(crate) struct JsonErrorResponse { -// pub(crate) status: String, -// pub(crate) message: String, -// pub(crate) code: u32, -// pub(crate) id: Option, -// pub(crate) item: Option, -// pub(crate) list: Option> -// } #[derive(Serialize, Default)] pub struct JsonResponseBuilder @@ -38,7 +29,7 @@ pub struct JsonResponseBuilder impl JsonResponseBuilder where T: serde::Serialize + Default { - fn new() -> Self { + pub(crate) fn new() -> Self { Self::default() } @@ -48,7 +39,7 @@ where T: serde::Serialize + Default } - fn set_list(mut self, list:Vec) -> Self { + pub(crate) fn set_list(mut self, list:Vec) -> Self { self.list = Some(list); self } @@ -66,6 +57,20 @@ where T: serde::Serialize + Default } )) } + + pub(crate) fn err(self) -> Result { + + Ok(web::Json( + JsonResponse { + status: self.status, + message: self.message, + code: self.code, + id: self.id, + item: self.item, + list: self.list, + } + )) + } } impl From for JsonResponseBuilder @@ -115,7 +120,8 @@ impl JsonResponse message: msg, code: 200, id: Some(id), - ..Default::default() + item: None, + list: None, } } @@ -123,8 +129,10 @@ impl JsonResponse JsonResponse { status: "Error".to_string(), code: 404, + id: None, + item: None, message: format!("Object not found"), - ..Default::default() + list: None, } } @@ -139,8 +147,10 @@ impl JsonResponse JsonResponse { status: "Error".to_string(), code: 500, + id: None, + item: None, message: msg, - ..Default::default() + list: None, } } @@ -155,72 +165,10 @@ impl JsonResponse JsonResponse { status: "Error".to_string(), code: 400, + id: None, + item: None, message: msg, - ..Default::default() + list: None, } } -} - -impl Default for JsonResponse { - - fn default() -> Self { - JsonResponse { - - status: "200".to_string(), - message: "OK".to_string(), - ..Default::default() - } - } -} - -// // Implement the Responder trait for GlobalResponse -// impl Responder for JsonResponse where T: { -// -// type Body = (); -// -// fn respond_to(self, _req: &HttpRequest) -> HttpResponse { -// HttpResponse::Ok().json(self) -// } -// } -// -// impl JsonErrorResponse { -// pub(crate) fn new(status: String, -// message: String, -// code: u32, -// id: Option, -// item:Option, -// list: Option>) -> Self { -// JsonErrorResponse { -// status, -// message, -// code, -// id, -// item, -// list, -// } -// } -// -// pub(crate) fn default() -> Self { -// JsonErrorResponse { -// status: "Internal Error".to_string(), -// message: "Internal Error".to_string(), -// code: 500, -// id: None, -// item: None, -// list: None, -// } -// } -// -// pub(crate) fn not_found() -> Self { -// JsonErrorResponse { -// status: "Error".to_string(), -// code: 404, -// message: format!("Object not found"), -// id: None, -// item: None, -// list: None -// } -// } -// -// -// } +} \ No newline at end of file diff --git a/src/routes/stack/compose.rs b/src/routes/stack/compose.rs index dc3230d..c7e7523 100644 --- a/src/routes/stack/compose.rs +++ b/src/routes/stack/compose.rs @@ -11,6 +11,7 @@ use actix_web::post; use sqlx::PgPool; use std::str; use tracing::Instrument; +use uuid::Uuid; use crate::helpers::stack::builder::DcBuilder; #[tracing::instrument(name = "Generate docker-compose.")] @@ -47,6 +48,62 @@ pub async fn add( } }; + match stack { + Some(stack) => { + let id = stack.id.clone(); + let mut dc = DcBuilder::new(stack); + let fc = dc.build(); + // tracing::debug!("Docker compose file content {:?}", fc.unwrap()); + return Ok(Json(JsonResponse::new( + "OK".to_owned(), + "Success".to_owned(), + 200, + Some(id), + Some(fc.unwrap()), + None + ))); + + } + None => { + return Ok(Json(JsonResponse::internal_error("Could not generate compose file"))); + } + } +} + +#[tracing::instrument(name = "Generate docker-compose.")] +#[post("/{id}/compose")] +pub async fn admin( + user: web::ReqData, + path: web::Path<(i32,)>, + pool: Data, +) -> Result { + /// Admin function for generating compose file for specified user + let id = path.0; + tracing::debug!("Received id: {}", id); + + let stack = match sqlx::query_as!( + Stack, + r#" + SELECT * FROM user_stack WHERE id=$1 LIMIT 1 + "#, + id, + ) + .fetch_one(pool.get_ref()) + .await + { + Ok(stack) => { + tracing::info!("stack found: {:?}", stack.id,); + Some(stack) + } + Err(sqlx::Error::RowNotFound) => { + tracing::error!("Row not found 404"); + None + } + Err(e) => { + tracing::error!("Failed to fetch stack, error: {:?}", e); + None + } + }; match stack { Some(stack) => { diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs index 1c871e5..27c3014 100644 --- a/src/routes/stack/deploy.rs +++ b/src/routes/stack/deploy.rs @@ -28,7 +28,7 @@ struct Payload { } -#[tracing::instrument(name = "Deploy.")] +#[tracing::instrument(name = "Deploy for every user. Admin endpoint")] #[post("/{id}/deploy")] pub async fn add( user: web::ReqData, @@ -42,9 +42,9 @@ pub async fn add( let stack = match sqlx::query_as!( Stack, r#" - SELECT * FROM user_stack WHERE id=$1 AND user_id=$2 LIMIT 1 + SELECT * FROM user_stack WHERE id=$1 LIMIT 1 "#, - id, user.id + id ) .fetch_one(pool.get_ref()) .await @@ -75,7 +75,7 @@ pub async fn add( let conn = Connection::connect(&addr, ConnectionProperties::default()) .await - .unwrap(); + .expect("Could not connect RabbitMQ"); tracing::info!("RABBITMQ CONNECTED"); diff --git a/src/routes/stack/get.rs b/src/routes/stack/get.rs index 786d905..24230b9 100644 --- a/src/routes/stack/get.rs +++ b/src/routes/stack/get.rs @@ -4,11 +4,12 @@ use crate::helpers::{JsonResponse, JsonResponseBuilder}; use crate::models; use crate::models::user::User; use std::convert::From; +use tracing::Instrument; #[tracing::instrument(name = "Get logged user stack.")] #[get("/{id}")] -pub async fn get( +pub async fn item( user: web::ReqData, path: web::Path<(i32,)>, pool: web::Data, @@ -55,3 +56,49 @@ pub async fn get( } } +#[tracing::instrument(name = "Get user's stack list.")] +#[get("/user/{id}")] +pub async fn list( + user: web::ReqData, + path: web::Path<(String,)>, + pool: web::Data, +) -> Result { + + /// This is admin endpoint, used by a m2m app, client app is confidential + /// it should return stacks by user id + /// in order to pass validation at external deployment service + + let (id,) = path.into_inner(); + tracing::info!("Logged user: {:?}", user.id); + tracing::info!("Get stack list for user {:?}", id); + + let query_span = tracing::info_span!("Get stacks by user id."); + + match sqlx::query_as!( + models::Stack, + r#" + SELECT * FROM user_stack WHERE user_id=$1 + "#, + id + ) + .fetch_all(pool.get_ref()) + .instrument(query_span) + .await + { + Ok(list) => { + let response_builder:JsonResponseBuilder = JsonResponseBuilder::new(); + return response_builder.set_list(list).ok(); + } + Err(sqlx::Error::RowNotFound) => { + tracing::error!("No stacks found for user: {:?}", &user.id); + let response_builder:JsonResponseBuilder = JsonResponseBuilder::default(); + return response_builder.ok(); + } + Err(e) => { + tracing::error!("Failed to fetch stack, error: {:?}", e); + let response_builder:JsonResponseBuilder = JsonResponseBuilder::default(); + return response_builder.ok(); + } + } +} + diff --git a/src/startup.rs b/src/startup.rs index b7fd324..c295136 100644 --- a/src/startup.rs +++ b/src/startup.rs @@ -130,7 +130,9 @@ pub async fn run( .service(crate::routes::stack::deploy::add) .service(crate::routes::stack::add::add) .service(crate::routes::stack::compose::add) - .service(crate::routes::stack::get::get) + .service(crate::routes::stack::compose::admin) + .service(crate::routes::stack::get::item) + .service(crate::routes::stack::get::list) ) .app_data(db_pool.clone()) .app_data(settings.clone()) From 5b6cf49973accb398b3f35084206f17f6d8dc116 Mon Sep 17 00:00:00 2001 From: vsilent Date: Mon, 13 Nov 2023 19:51:24 +0200 Subject: [PATCH 09/18] new form struct, new fields --- src/forms/stack.rs | 18 +++++++++--------- src/routes/stack/add.rs | 7 ++++--- src/routes/stack/compose.rs | 4 ++-- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/forms/stack.rs b/src/forms/stack.rs index 0ee3ec6..c49ae97 100644 --- a/src/forms/stack.rs +++ b/src/forms/stack.rs @@ -6,8 +6,9 @@ use serde_valid::Validate; #[serde(rename_all = "snake_case")] pub struct StackForm { #[serde(rename= "commonDomain")] - pub common_domain: String, + pub common_domain: Option, pub domain_list: Option, + pub stack_code: Option, pub region: String, pub zone: Option, pub server: String, @@ -19,13 +20,13 @@ pub struct StackForm { #[serde(rename = "extended_features")] pub extended_features: Option>, pub subscriptions: Option>, + pub form_app: Option>, + pub disk_type: Option, #[serde(rename = "save_token")] pub save_token: bool, #[serde(rename = "cloud_token")] pub cloud_token: String, pub provider: String, - #[serde(rename = "stack_code")] - pub stack_code: String, #[serde(rename = "selected_plan")] pub selected_plan: String, pub custom: Custom, @@ -52,6 +53,8 @@ pub struct StackPayload { #[serde(rename = "extended_features")] pub extended_features: Option>, pub subscriptions: Option>, + pub form_app: Option>, + pub disk_type: Option, #[serde(rename = "save_token")] pub save_token: bool, #[serde(rename = "cloud_token")] @@ -87,12 +90,10 @@ pub struct Custom { pub service: Option>, #[serde(rename = "servers_count")] pub servers_count: i64, - #[serde(rename = "custom_stack_name")] - pub custom_stack_name: Option, #[serde(rename = "custom_stack_code")] pub custom_stack_code: String, - #[serde(rename = "custom_stack_git_url")] - pub custom_stack_git_url: Option, + #[serde(rename = "project_git_url")] + pub project_git_url: Option, #[serde(rename = "custom_stack_category")] pub custom_stack_category: Option>, #[serde(rename = "custom_stack_short_description")] @@ -187,8 +188,7 @@ pub struct Feature { pub ansible_var: Option, #[serde(rename = "repo_dir")] pub repo_dir: Option, - #[validate(min_length=1)] - pub cpu: String, + pub cpu: f64, #[validate(min_length=1)] #[serde(rename = "ram_size")] pub ram_size: String, diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs index 64c65cb..9239bc6 100644 --- a/src/routes/stack/add.rs +++ b/src/routes/stack/add.rs @@ -32,7 +32,8 @@ pub async fn add( f } Err(_err) => { - return Ok(Json(JsonResponse::::not_valid("Invalid data"))); + let msg = format!("Invalid data. {:?}", _err); + return Ok(Json(JsonResponse::::not_valid(msg.as_str()))); } }; @@ -40,7 +41,7 @@ pub async fn add( let request_id = Uuid::new_v4(); let request_span = tracing::info_span!( "Validating a new stack", %request_id, - commonDomain=?&form.common_domain, + commonDomain=?&form.custom.project_name, region=?&form.region, domainList=?&form.domain_list ); @@ -50,7 +51,7 @@ pub async fn add( tracing::info!( "request_id {} Adding '{}' '{}' as a new stack", request_id, - form.common_domain, + form.custom.project_name, form.region ); diff --git a/src/routes/stack/compose.rs b/src/routes/stack/compose.rs index c7e7523..0810664 100644 --- a/src/routes/stack/compose.rs +++ b/src/routes/stack/compose.rs @@ -14,7 +14,7 @@ use tracing::Instrument; use uuid::Uuid; use crate::helpers::stack::builder::DcBuilder; -#[tracing::instrument(name = "Generate docker-compose.")] +#[tracing::instrument(name = "User's generate docker-compose.")] #[post("/{id}")] pub async fn add( user: web::ReqData, @@ -70,7 +70,7 @@ pub async fn add( } } -#[tracing::instrument(name = "Generate docker-compose.")] +#[tracing::instrument(name = "Generate docker-compose. Admin")] #[post("/{id}/compose")] pub async fn admin( user: web::ReqData, From c4a1de203315cb6f063dfe8ccd6f832e7604cda6 Mon Sep 17 00:00:00 2001 From: vsilent Date: Tue, 14 Nov 2023 08:56:56 +0200 Subject: [PATCH 10/18] migration fix --- migrations/20230905145525_creating_stack_tables.up.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/20230905145525_creating_stack_tables.up.sql b/migrations/20230905145525_creating_stack_tables.up.sql index c2d248c..52ff531 100644 --- a/migrations/20230905145525_creating_stack_tables.up.sql +++ b/migrations/20230905145525_creating_stack_tables.up.sql @@ -8,6 +8,6 @@ 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 user_stack_pkey PRIMARY KEY (id) ) From 5e3ee514a56a6d0cad8c5a5ae14b52c4a67b9660 Mon Sep 17 00:00:00 2001 From: vsilent Date: Tue, 14 Nov 2023 10:10:10 +0200 Subject: [PATCH 11/18] indexes, cpu optional --- .../20230905145525_creating_stack_tables.up.sql | 8 ++++---- src/forms/stack.rs | 11 +++++------ 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/migrations/20230905145525_creating_stack_tables.up.sql b/migrations/20230905145525_creating_stack_tables.up.sql index 52ff531..b20b2cd 100644 --- a/migrations/20230905145525_creating_stack_tables.up.sql +++ b/migrations/20230905145525_creating_stack_tables.up.sql @@ -1,5 +1,3 @@ --- Add up migration script here --- Add migration script here CREATE TABLE user_stack ( id serial4 NOT NULL, stack_id uuid NOT NULL, @@ -8,6 +6,8 @@ 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 user_stack_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 diff --git a/src/forms/stack.rs b/src/forms/stack.rs index c49ae97..6fe40ac 100644 --- a/src/forms/stack.rs +++ b/src/forms/stack.rs @@ -143,7 +143,7 @@ pub struct Web { #[validate(pattern = r"^\d+G$")] pub ram_size: String, #[validate(minimum=0.1)] - pub cpu: f64, + pub cpu: Option, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)] @@ -188,13 +188,13 @@ pub struct Feature { pub ansible_var: Option, #[serde(rename = "repo_dir")] pub repo_dir: Option, - pub cpu: f64, + pub cpu: Option, #[validate(min_length=1)] #[serde(rename = "ram_size")] - pub ram_size: String, + pub ram_size: Option, #[validate(min_length=1)] #[serde(rename = "disk_size")] - pub disk_size: String, + pub disk_size: Option, #[serde(rename = "dockerhub_image")] pub dockerhub_image: Option, pub versions: Option>, @@ -293,8 +293,7 @@ pub struct Service { pub ansible_var: Option, #[serde(rename = "repo_dir")] pub repo_dir: Option, - #[validate(min_length=1)] - pub cpu: String, + pub cpu: Option, #[serde(rename = "ram_size")] #[validate(min_length=1)] pub ram_size: String, From 4dadfa3147f7c6bc7547c8e6721f914b23c69697 Mon Sep 17 00:00:00 2001 From: vsilent Date: Wed, 15 Nov 2023 14:17:19 +0200 Subject: [PATCH 12/18] pass stack id --- src/forms/stack.rs | 1 + src/routes/stack/compose.rs | 4 ++-- src/routes/stack/deploy.rs | 10 ++-------- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/forms/stack.rs b/src/forms/stack.rs index 6fe40ac..4f3afd9 100644 --- a/src/forms/stack.rs +++ b/src/forms/stack.rs @@ -36,6 +36,7 @@ pub struct StackForm { #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)] #[serde(rename_all = "snake_case")] pub struct StackPayload { + pub(crate) id: i32, pub(crate) user_token: Option, pub(crate) user_email: Option, pub(crate) installation_id: Option, diff --git a/src/routes/stack/compose.rs b/src/routes/stack/compose.rs index 0810664..555d7dc 100644 --- a/src/routes/stack/compose.rs +++ b/src/routes/stack/compose.rs @@ -7,7 +7,7 @@ use actix_web::{ use crate::helpers::JsonResponse; use crate::models::user::User; use crate::models::Stack; -use actix_web::post; +use actix_web::{get, post}; use sqlx::PgPool; use std::str; use tracing::Instrument; @@ -71,7 +71,7 @@ pub async fn add( } #[tracing::instrument(name = "Generate docker-compose. Admin")] -#[post("/{id}/compose")] +#[get("/{id}/compose")] pub async fn admin( user: web::ReqData, path: web::Path<(i32,)>, diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs index 27c3014..768da66 100644 --- a/src/routes/stack/deploy.rs +++ b/src/routes/stack/deploy.rs @@ -20,13 +20,6 @@ use serde::Serialize; use crate::forms::{StackForm, StackPayload}; -#[derive(Serialize, Debug, Clone)] -struct Payload { - user_token: String, - user_email: String, - installation_id: String, -} - #[tracing::instrument(name = "Deploy for every user. Admin endpoint")] #[post("/{id}/deploy")] @@ -84,7 +77,8 @@ pub async fn add( dc.stack.body.clone() ).unwrap(); - stack_data.installation_id = Some(1); + stack_data.id = id; + stack_data.installation_id = None; stack_data.user_token = Some(user.id.clone()); stack_data.user_email= Some(user.email.clone()); From 942bef36130ea3cf12f36a8356a13b7746d98328 Mon Sep 17 00:00:00 2001 From: vsilent Date: Thu, 16 Nov 2023 15:44:10 +0200 Subject: [PATCH 13/18] rename sharedPorts to shared_ports --- configuration.yaml | 2 +- custom-stack-payload-5.json | 2 +- custom-stack-payload-6.json | 1 + src/forms/stack.rs | 17 +++++++++++++---- src/routes/stack/deploy.rs | 5 +++-- 5 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 custom-stack-payload-6.json diff --git a/configuration.yaml b/configuration.yaml index 5a111da..af99721 100644 --- a/configuration.yaml +++ b/configuration.yaml @@ -10,7 +10,7 @@ database: database_name: stacker amqp: - host: 51.15.74.139 + host: 127.0.0.1 port: 5672 username: guest password: guest diff --git a/custom-stack-payload-5.json b/custom-stack-payload-5.json index a8691db..70bda71 100644 --- a/custom-stack-payload-5.json +++ b/custom-stack-payload-5.json @@ -1 +1 @@ -{"commonDomain":"test.app", "domainList":{}, "region":"fsn1", "zone":null, "server":"cx21", "os":"ubuntu-20.04", "ssl":"letsencrypt", "vars":[], "integrated_features":[],"extended_features":[],"subscriptions":["stack_migration"],"save_token":false,"cloud_token":"r6LAjqrynVt7pUwctVkzBlJmKjLOCxJIWjZFMLTkPYCCB4rsgphhEVhiL4DuO757","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"name":"Smarty Bot","code":"smarty-bot","domain":"smartybot.xyz","sharedPorts":["8000"],"versions":[],"custom":true,"type":"web","main":true,"_id":"lltkpq6p347kystct","dockerhub_user":"trydirect","dockerhub_name":"smarty-bot","url_app":"smartybot.xyz","url_git":"https://github.com/vsilent/smarty.git","disk_size":"1Gb","ram_size":"1Gb","cpu":1}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"

Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"0.6","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","versions":[{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}],"domain":"","sharedPorts":["9000"],"main":true,"version":{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}}],"service":[{"_etag":null,"_id":230,"_created":"2023-05-24T12:51:52.108972","_updated":"2023-08-04T12:18:34.670194","name":"pgrst","code":"pgrst","role":null,"type":"service","default":null,"popularity":null,"descr":null,"ports":null,"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":null,"category_id":null,"parent_app_id":null,"full_description":null,"description":"

PostgREST description

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":"1","ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"pgrst","versions":[{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"},{"_etag":null,"_id":563,"_created":null,"_updated":"2023-05-24T12:52:15.351522","app_id":230,"name":"0.0.5","version":"0.0.5","update_status":"ready_for_testing","tag":"0.0.5"}],"domain":"","sharedPorts":["9999"],"main":true,"version":{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"}}],"servers_count":3,"custom_stack_name":"mysampleproject5","custom_stack_code":"another-bot5","custom_stack_category":["New"],"custom_stack_short_description":"sample short description","custom_stack_description":"stack description","custom_stack_publish":false,"project_name":"Smarty Bot","project_git_url":"https://github.com/vsilent/smarty.git","project_overview":"my product 1","project_description":"my product 1"}} +{"commonDomain":"test.app", "domainList":{}, "region":"fsn1", "zone":null, "server":"cx21", "os":"ubuntu-20.04", "ssl":"letsencrypt", "vars":[], "integrated_features":[],"extended_features":[],"subscriptions":["stack_migration"],"save_token":false,"cloud_token":"r6LAjqrynVt7pUwctVkzBlJmKjLOCxJIWjZFMLTkPYCCB4rsgphhEVhiL4DuO757","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"name":"Smarty Bot","code":"smarty-bot","domain":"smartybot.xyz","sharedPorts":["8000"],"versions":[],"custom":true,"type":"web","main":true,"_id":"lltkpq6p347kystct","dockerhub_user":"trydirect","dockerhub_name":"smarty-bot","url_app":"smartybot.xyz","url_git":"https://github.com/vsilent/smarty.git","disk_size":"1Gb","ram_size":"1Gb","cpu":1}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"

Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0.6,"ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","versions":[{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}],"domain":"","sharedPorts":["9000"],"main":true,"version":{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}}],"service":[{"_etag":null,"_id":230,"_created":"2023-05-24T12:51:52.108972","_updated":"2023-08-04T12:18:34.670194","name":"pgrst","code":"pgrst","role":null,"type":"service","default":null,"popularity":null,"descr":null,"ports":null,"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":null,"category_id":null,"parent_app_id":null,"full_description":null,"description":"

PostgREST description

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":1,"ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"pgrst","versions":[{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"},{"_etag":null,"_id":563,"_created":null,"_updated":"2023-05-24T12:52:15.351522","app_id":230,"name":"0.0.5","version":"0.0.5","update_status":"ready_for_testing","tag":"0.0.5"}],"domain":"","sharedPorts":["9999"],"main":true,"version":{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"}}],"servers_count":3,"custom_stack_name":"mysampleproject5","custom_stack_code":"another-bot5","custom_stack_category":["New"],"custom_stack_short_description":"sample short description","custom_stack_description":"stack description","custom_stack_publish":false,"project_name":"Smarty Bot","project_git_url":"https://github.com/vsilent/smarty.git","project_overview":"my product 1","project_description":"my product 1"}} diff --git a/custom-stack-payload-6.json b/custom-stack-payload-6.json new file mode 100644 index 0000000..97e422a --- /dev/null +++ b/custom-stack-payload-6.json @@ -0,0 +1 @@ +{"commonDomain":"test.app", "domainList":{}, "region":"fsn1", "zone":null, "server":"cx21", "os":"ubuntu-20.04", "ssl":"letsencrypt", "vars":[], "integrated_features":[],"extended_features":[],"subscriptions":["stack_migration"],"save_token":false,"cloud_token":"r6LAjqrynVt7pUwctVkzBlJmKjLOCxJIWjZFMLTkPYCCB4rsgphhEVhiL4DuO757","provider":"htz","stack_code":"another-bot8","selected_plan":"plan-individual-monthly","custom":{"web":[{"name":"Smarty Bot","code":"smarty-bot","domain":"smartybot.xyz","sharedPorts":["8000"],"versions":[],"custom":true,"type":"web","main":true,"_id":"lltkpq6p347kystct","dockerhub_user":"trydirect","dockerhub_name":"smarty-bot","url_app":"smartybot.xyz","url_git":"https://github.com/vsilent/smarty.git","disk_size":"1Gb","ram_size":"1Gb","cpu":1}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"

Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0.6,"ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","versions":[{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}],"domain":"","sharedPorts":["9000"],"main":true,"version":{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}}],"service":[{"_etag":null,"_id":230,"_created":"2023-05-24T12:51:52.108972","_updated":"2023-08-04T12:18:34.670194","name":"pgrst","code":"pgrst","role":null,"type":"service","default":null,"popularity":null,"descr":null,"ports":null,"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":null,"category_id":null,"parent_app_id":null,"full_description":null,"description":"

PostgREST description

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":1,"ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"pgrst","versions":[{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"},{"_etag":null,"_id":563,"_created":null,"_updated":"2023-05-24T12:52:15.351522","app_id":230,"name":"0.0.5","version":"0.0.5","update_status":"ready_for_testing","tag":"0.0.5"}],"domain":"","sharedPorts":["9999"],"main":true,"version":{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"}}],"servers_count":3,"custom_stack_name":"mysampleproject6","custom_stack_code":"another-bot8","custom_stack_category":["New"],"custom_stack_short_description":"sample short description","custom_stack_description":"stack description","custom_stack_publish":false,"project_name":"Smarty Bot","project_git_url":"https://github.com/vsilent/smarty.git","project_overview":"my product 1","project_description":"my product 1"}} diff --git a/src/forms/stack.rs b/src/forms/stack.rs index 4f3afd9..50ca124 100644 --- a/src/forms/stack.rs +++ b/src/forms/stack.rs @@ -36,7 +36,7 @@ pub struct StackForm { #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)] #[serde(rename_all = "snake_case")] pub struct StackPayload { - pub(crate) id: i32, + pub(crate) id: Option, pub(crate) user_token: Option, pub(crate) user_email: Option, pub(crate) installation_id: Option, @@ -116,7 +116,10 @@ pub struct Web { pub name: String, pub code: String, pub domain: Option, - #[serde(rename = "shared_ports")] + //#[serde(rename = "shared_ports")] + // #[serde(rename= "sharedPorts")] + #[serde(rename(deserialize = "sharedPorts"))] + #[serde(rename(serialize = "shared_ports"))] pub shared_ports: Option>, pub versions: Option>, pub custom: bool, @@ -200,7 +203,10 @@ pub struct Feature { pub dockerhub_image: Option, pub versions: Option>, pub domain: Option, - #[serde(rename = "shared_ports")] + // #[serde(rename = "shared_ports")] + // #[serde(rename= "sharedPorts")] + #[serde(rename(deserialize = "sharedPorts"))] + #[serde(rename(serialize = "shared_ports"))] pub shared_ports: Option>, pub main: bool, } @@ -305,7 +311,10 @@ pub struct Service { pub dockerhub_image: Option, pub versions: Option>, pub domain: String, - #[serde(rename = "shared_ports")] + // #[serde(rename = "shared_ports")] + // #[serde(rename= "sharedPorts")] + #[serde(rename(deserialize = "sharedPorts"))] + #[serde(rename(serialize = "shared_ports"))] pub shared_ports: Option>, pub main: bool, } diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs index 768da66..aaf7135 100644 --- a/src/routes/stack/deploy.rs +++ b/src/routes/stack/deploy.rs @@ -77,10 +77,11 @@ pub async fn add( dc.stack.body.clone() ).unwrap(); - stack_data.id = id; + stack_data.id = Some(id); stack_data.installation_id = None; stack_data.user_token = Some(user.id.clone()); - stack_data.user_email= Some(user.email.clone()); + stack_data.user_email = Some(user.email.clone()); + stack_data.stack_code = stack_data.custom.custom_stack_code.clone(); let payload = serde_json::to_string::(&stack_data).unwrap(); let _payload = payload.as_bytes(); From d33f33a0d08a91fb700ec1c0947abbe26cb78ec9 Mon Sep 17 00:00:00 2001 From: vsilent Date: Thu, 16 Nov 2023 17:00:35 +0200 Subject: [PATCH 14/18] service option fields fixes --- custom-stack-payload-7.json | 1 + custom-stack-payload-8.json | 1 + src/forms/stack.rs | 37 ++++++++++++++++++------------------ src/helpers/stack/builder.rs | 8 +++++++- 4 files changed, 27 insertions(+), 20 deletions(-) create mode 100644 custom-stack-payload-7.json create mode 100644 custom-stack-payload-8.json diff --git a/custom-stack-payload-7.json b/custom-stack-payload-7.json new file mode 100644 index 0000000..61dedb9 --- /dev/null +++ b/custom-stack-payload-7.json @@ -0,0 +1 @@ +{"commonDomain":"test.app", "domainList":{}, "region":"fsn1", "zone":null, "server":"cx21", "os":"ubuntu-20.04", "ssl":"letsencrypt", "vars":[], "integrated_features":[],"extended_features":[],"subscriptions":["stack_migration"],"save_token":false,"cloud_token":"r6LAjqrynVt7pUwctVkzBlJmKjLOCxJIWjZFMLTkPYCCB4rsgphhEVhiL4DuO757","provider":"htz","stack_code":"another-bot8","selected_plan":"plan-individual-monthly","custom":{"web":[{"name":"Smarty Bot","code":"smarty-bot","domain":"smartybot.xyz","sharedPorts":["8000"],"versions":[],"custom":true,"type":"web","main":true,"_id":0,"dockerhub_user":"trydirect","dockerhub_name":"smarty-bot","url_app":"smartybot.xyz","url_git":"https://github.com/vsilent/smarty.git","disk_size":"1Gb","ram_size":"1Gb","cpu":1}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"

Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0.6,"ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","versions":[{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}],"domain":"","sharedPorts":["9000"],"main":true,"version":{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}}],"service":[{"_etag":null,"_id":230,"_created":"2023-05-24T12:51:52.108972","_updated":"2023-08-04T12:18:34.670194","name":"pgrst","code":"pgrst","role":null,"type":"service","default":null,"popularity":null,"descr":null,"ports":null,"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":null,"category_id":null,"parent_app_id":null,"full_description":null,"description":"

PostgREST description

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":1,"ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"pgrst","versions":[{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"},{"_etag":null,"_id":563,"_created":null,"_updated":"2023-05-24T12:52:15.351522","app_id":230,"name":"0.0.5","version":"0.0.5","update_status":"ready_for_testing","tag":"0.0.5"}],"domain":"","sharedPorts":["9999"],"main":true,"version":{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"}}],"servers_count":3,"custom_stack_name":"mysampleproject6","custom_stack_code":"another-bot8","custom_stack_category":["New"],"custom_stack_short_description":"sample short description","custom_stack_description":"stack description","custom_stack_publish":false,"project_name":"Smarty Bot","project_git_url":"https://github.com/vsilent/smarty.git","project_overview":"my product 1","project_description":"my product 1"}} diff --git a/custom-stack-payload-8.json b/custom-stack-payload-8.json new file mode 100644 index 0000000..49d2786 --- /dev/null +++ b/custom-stack-payload-8.json @@ -0,0 +1 @@ +{"commonDomain":"","domainList":{},"region":"fsn1","zone":null,"server":"cpx11","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":false,"disk_type":"pd-standart","cloud_token":"****","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"_etag":null,"_id":233,"_created":"2023-07-19T06:38:57.608807","_updated":"2023-08-15T11:12:14.921797","name":"FastAPI","code":"fastapi","role":null,"type":"web","default":true,"popularity":null,"descr":null,"ports":{"public":["5050","8000","8080"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":2500,"height":2500,"image":"8d1ba06d-04e2-4523-879e-2846c86a10d8.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":null,"plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0.1,"ram_size":"0.2Gb","disk_size":"0.2Gb","dockerhub_image":"fastapi","form":null,"versions":[{"_etag":null,"_id":587,"_created":null,"_updated":"2023-07-20T12:51:20.321999","app_id":233,"name":"0.0.1","version":"0.0.1","update_status":"ready_for_testing","tag":"0.0.1"},{"_etag":null,"_id":590,"_created":null,"_updated":"2023-07-20T08:36:49.651219","app_id":233,"name":"0.0.1","version":"0.0.1","update_status":"ready_for_testing","tag":"latest"},{"_etag":null,"_id":589,"_created":null,"_updated":"2023-07-20T08:36:55.200575","app_id":233,"name":"0.0.1","version":"0.0.1","update_status":"ready_for_testing","tag":"unstable"},{"_etag":"591","_id":591,"_created":null,"_updated":"2023-08-15T08:17:40.226186","app_id":233,"name":"Fastapi","version":"0.100.0","update_status":"published","tag":"stable"}],"domain":"fastapi.test","sharedPorts":["8000"],"main":true,"version":{"_etag":"591","_id":591,"_created":null,"_updated":"2023-08-15T08:17:40.226186","app_id":233,"name":"Fastapi","version":"0.100.0","update_status":"published","tag":"stable"}}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"

Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)

","plan_type":null,"ansible_var":null,"repo_Show more diff --git a/src/forms/stack.rs b/src/forms/stack.rs index 50ca124..108d1f3 100644 --- a/src/forms/stack.rs +++ b/src/forms/stack.rs @@ -3,7 +3,6 @@ use serde_json::Value; use serde_valid::Validate; #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)] -#[serde(rename_all = "snake_case")] pub struct StackForm { #[serde(rename= "commonDomain")] pub common_domain: Option, @@ -15,19 +14,14 @@ pub struct StackForm { pub os: String, pub ssl: String, pub vars: Option>, - #[serde(rename = "integrated_features")] pub integrated_features: Option>, - #[serde(rename = "extended_features")] pub extended_features: Option>, pub subscriptions: Option>, pub form_app: Option>, pub disk_type: Option, - #[serde(rename = "save_token")] pub save_token: bool, - #[serde(rename = "cloud_token")] pub cloud_token: String, pub provider: String, - #[serde(rename = "selected_plan")] pub selected_plan: String, pub custom: Custom, } @@ -90,7 +84,7 @@ pub struct Custom { pub feature: Option>, pub service: Option>, #[serde(rename = "servers_count")] - pub servers_count: i64, + pub servers_count: u32, #[serde(rename = "custom_stack_code")] pub custom_stack_code: String, #[serde(rename = "project_git_url")] @@ -122,16 +116,19 @@ pub struct Web { #[serde(rename(serialize = "shared_ports"))] pub shared_ports: Option>, pub versions: Option>, - pub custom: bool, + pub custom: Option, #[serde(rename = "type")] pub type_field: String, pub main: bool, #[serde(rename = "_id")] - pub id: String, + pub id: u32, #[serde(rename = "dockerhub_user")] - pub dockerhub_user: String, + pub dockerhub_user: Option, #[serde(rename = "dockerhub_name")] - pub dockerhub_name: String, + pub dockerhub_name: Option, + // when no docker repo specified by user + // use default TD image + pub dockerhub_image: Option, #[serde(rename = "url_app")] pub url_app: Option, #[serde(rename = "url_git")] @@ -145,7 +142,7 @@ pub struct Web { #[validate(min_length=1)] #[validate(max_length=10)] #[validate(pattern = r"^\d+G$")] - pub ram_size: String, + pub ram_size: Option, #[validate(minimum=0.1)] pub cpu: Option, } @@ -194,7 +191,8 @@ pub struct Feature { pub repo_dir: Option, pub cpu: Option, #[validate(min_length=1)] - #[serde(rename = "ram_size")] + #[validate(max_length=10)] + #[validate(pattern = r"^\d+G$")] pub ram_size: Option, #[validate(min_length=1)] #[serde(rename = "disk_size")] @@ -244,13 +242,13 @@ pub struct Version { #[serde(rename = "_etag")] pub etag: Option, #[serde(rename = "_id")] - pub id: i64, + pub id: u32, #[serde(rename = "_created")] pub created: Option, #[serde(rename = "_updated")] pub updated: Option, #[serde(rename = "app_id")] - pub app_id: i64, + pub app_id: u32, pub name: String, pub version: String, #[serde(rename = "update_status")] @@ -264,7 +262,7 @@ pub struct Service { #[serde(rename = "_etag")] pub etag: Option, #[serde(rename = "_id")] - pub id: i64, + pub id: u32, #[serde(rename = "_created")] pub created: Option, #[serde(rename = "_updated")] @@ -301,12 +299,13 @@ pub struct Service { #[serde(rename = "repo_dir")] pub repo_dir: Option, pub cpu: Option, - #[serde(rename = "ram_size")] #[validate(min_length=1)] - pub ram_size: String, + #[validate(max_length=10)] + #[validate(pattern = r"^\d+G$")] + pub ram_size: Option, #[serde(rename = "disk_size")] #[validate(min_length=1)] - pub disk_size: String, + pub disk_size: Option, #[serde(rename = "dockerhub_image")] pub dockerhub_image: Option, pub versions: Option>, diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs index a6c9fd9..3986650 100644 --- a/src/helpers/stack/builder.rs +++ b/src/helpers/stack/builder.rs @@ -89,7 +89,13 @@ impl DcBuilder { for app in apps.custom.web { let tag = "latest"; - let img= format!("{}/{}:{}",app.dockerhub_user, app.dockerhub_name, tag); + let dim = app.dockerhub_image.clone().unwrap_or("".to_string()); + let img= format!("{}/{}:{}", + app.dockerhub_user.clone() + .unwrap_or("trydirect".to_string()).clone(), + app.dockerhub_name.clone().unwrap_or(dim), + tag + ); let code = app.code.clone().to_owned(); let mut service = Service { From 2e209bed2b8de7c52f0896743f73e190be67d885 Mon Sep 17 00:00:00 2001 From: vsilent Date: Fri, 17 Nov 2023 09:55:20 +0200 Subject: [PATCH 15/18] insert stack fix --- custom-stack-payload-9.json | 1 + src/forms/stack.rs | 3 +++ src/routes/stack/add.rs | 28 ++++++++-------------------- 3 files changed, 12 insertions(+), 20 deletions(-) create mode 100644 custom-stack-payload-9.json diff --git a/custom-stack-payload-9.json b/custom-stack-payload-9.json new file mode 100644 index 0000000..3bf473d --- /dev/null +++ b/custom-stack-payload-9.json @@ -0,0 +1 @@ +{"commonDomain":"","domainList":{},"region":"fsn1","zone":null,"server":"cx21","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":["stack_migration"],"form_app":[],"save_token":true,"disk_type":"pd-standart","cloud_token":"D3dkDL4Qy1NpFezTl60V5RYfB5p0BSwLLoKVHhEqmJQ3jObSG77irP86e9YtCYVi","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"_etag":null,"_id":233,"_created":"2023-07-19T06:38:57.608807","_updated":"2023-08-15T11:12:14.921797","name":"FastAPI","code":"fastapi","role":null,"type":"web","default":true,"popularity":null,"descr":null,"ports":{"public":["5050","8000","8080"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":2500,"height":2500,"image":"8d1ba06d-04e2-4523-879e-2846c86a10d8.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":null,"plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0.1,"ram_size":"0.2Gb","disk_size":"0.2Gb","dockerhub_image":"fastapi","form":null,"versions":[{"_etag":null,"_id":587,"_created":null,"_updated":"2023-07-20T12:51:20.321999","app_id":233,"name":"0.0.1","version":"0.0.1","update_status":"ready_for_testing","tag":"0.0.1"},{"_etag":null,"_id":590,"_created":null,"_updated":"2023-07-20T08:36:49.651219","app_id":233,"name":"0.0.1","version":"0.0.1","update_status":"ready_for_testing","tag":"latest"},{"_etag":null,"_id":589,"_created":null,"_updated":"2023-07-20T08:36:55.200575","app_id":233,"name":"0.0.1","version":"0.0.1","update_status":"ready_for_testing","tag":"unstable"},{"_etag":"591","_id":591,"_created":null,"_updated":"2023-08-15T08:17:40.226186","app_id":233,"name":"Fastapi","version":"0.100.0","update_status":"published","tag":"stable"}],"domain":"fastapi.test","sharedPorts":["8000"],"main":true,"version":{"_etag":"591","_id":591,"_created":null,"_updated":"2023-08-15T08:17:40.226186","app_id":233,"name":"Fastapi","version":"0.100.0","update_status":"published","tag":"stable"}},{"name":"smarty bot","code":"smarty-bot","domain":"smarty-bot.fastapi.test","sharedPorts":["8080"],"versions":[],"custom":true,"type":"web","main":false,"_id":0,"dockerhub_user":"vsilent","dockerhub_name":"smarty","disk_size":"1Gb","ram_size":"1Gb","cpu":1}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"

Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0.6,"ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","form":null,"versions":[{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-09-07T07:28:51.18965","app_id":198,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"}],"domain":"","sharedPorts":["9000"],"main":false,"version":{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-09-07T07:28:51.18965","app_id":198,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"}}],"service":[{"_etag":null,"_id":246,"_created":"2023-09-15T11:41:21.353013","_updated":"2023-09-15T11:45:40.307663","name":"Mailhog","code":"mailhog_service","role":["mailhog"],"type":"service","default":null,"popularity":null,"descr":null,"ports":{"public":["8025"],"private":["1025","8025"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":48,"height":48,"image":"9ed27fd5-4745-4fa1-86a6-e53557d700ff.png"},"dark":{"width":48,"height":48,"image":"0d7b38f8-d2ad-4068-a5bd-aa32c7e1958f.png"}},"category_id":null,"parent_app_id":null,"full_description":null,"description":"

MailHog is an email testing tool for developers.

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0,"ram_size":null,"disk_size":null,"dockerhub_image":null,"form":null,"versions":[{"_etag":null,"_id":625,"_created":null,"_updated":"2023-09-15T11:42:10.48131","app_id":246,"name":"latest","version":"latest","update_status":"ready_for_production","tag":"latest"}],"domain":"","sharedPorts":["8025"],"main":false,"version":{"_etag":null,"_id":625,"_created":null,"_updated":"2023-09-15T11:42:10.48131","app_id":246,"name":"latest","version":"latest","update_status":"ready_for_production","tag":"latest"}},{"_etag":null,"_id":24,"_created":"2020-06-19T13:07:24.228389","_updated":"2023-08-08T10:34:13.4985","name":"PostgreSQL","code":"postgres","role":[],"type":"service","default":null,"popularity":null,"descr":null,"ports":null,"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":576,"height":594,"image":"fd23f54c-e250-4228-8d56-7e5d93ffb925.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":null,"plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0,"ram_size":null,"disk_size":null,"dockerhub_image":"postgres","form":null,"versions":[{"_etag":null,"_id":458,"_created":"2022-10-20T07:57:05.88997","_updated":"2023-04-05T07:24:39.637749","app_id":24,"name":"15","version":"15","update_status":"published","tag":"15"},{"_etag":null,"_id":288,"_created":"2022-10-20T07:56:16.160116","_updated":"2023-03-17T13:46:51.433539","app_id":24,"name":"10.22","version":"10.22","update_status":"published","tag":"10.22"},{"_etag":null,"_id":303,"_created":"2022-10-20T07:57:24.710286","_updated":"2023-03-17T13:46:51.433539","app_id":24,"name":"13.8","version":"13.8","update_status":"published","tag":"13.8"},{"_etag":null,"_id":266,"_created":"2022-10-20T07:56:32.360852","_updated":"2023-04-05T06:49:31.782132","app_id":24,"name":"11","version":"11","update_status":"published","tag":"11"},{"_etag":null,"_id":267,"_created":"2022-10-20T07:57:35.552085","_updated":"2023-03-17T13:46:51.433539","app_id":24,"name":"12.12","version":"12.12","update_status":"published","tag":"12.12"},{"_etag":null,"_id":38,"_created":"2020-06-19T13:07:24.258724","_updated":"2022-10-20T07:58:06.882602","app_id":24,"name":"14.5","version":"14.5","update_status":"published","tag":"14.5"},{"_etag":null,"_id":564,"_created":null,"_updated":"2023-05-24T12:55:57.894215","app_id":24,"name":"0.0.5","version":"0.0.5","update_status":"ready_for_testing","tag":"0.0.5"},{"_etag":null,"_id":596,"_created":null,"_updated":"2023-08-09T11:00:33.004267","app_id":24,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"}],"domain":"","sharedPorts":["6372"],"main":false,"version":{"_etag":null,"_id":596,"_created":null,"_updated":"2023-08-09T11:00:33.004267","app_id":24,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"}}],"servers_count":3,"project_name":"FastAPI example 3","custom_stack_code":"fastapi-example-3","project_git_url":"https://github.com/trydirect/fastapi.git"}} diff --git a/src/forms/stack.rs b/src/forms/stack.rs index 108d1f3..541412c 100644 --- a/src/forms/stack.rs +++ b/src/forms/stack.rs @@ -114,6 +114,7 @@ pub struct Web { // #[serde(rename= "sharedPorts")] #[serde(rename(deserialize = "sharedPorts"))] #[serde(rename(serialize = "shared_ports"))] + #[serde(alias = "shared_ports")] pub shared_ports: Option>, pub versions: Option>, pub custom: Option, @@ -205,6 +206,7 @@ pub struct Feature { // #[serde(rename= "sharedPorts")] #[serde(rename(deserialize = "sharedPorts"))] #[serde(rename(serialize = "shared_ports"))] + #[serde(alias = "shared_ports")] pub shared_ports: Option>, pub main: bool, } @@ -314,6 +316,7 @@ pub struct Service { // #[serde(rename= "sharedPorts")] #[serde(rename(deserialize = "sharedPorts"))] #[serde(rename(serialize = "shared_ports"))] + #[serde(alias = "shared_ports")] pub shared_ports: Option>, pub main: bool, } diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs index 9239bc6..2ae44a4 100644 --- a/src/routes/stack/add.rs +++ b/src/routes/stack/add.rs @@ -66,30 +66,18 @@ pub async fn add( } }; - let stack = Stack { - id: 0_i32, // internal stack id - stack_id: Uuid::new_v4(), // public uuid of the stack - user_id: user_id, // - name: stack_name, - body: body, - created_at: Utc::now(), - updated_at: Utc::now(), - }; - - tracing::debug!("stack object {:?}", stack); return match sqlx::query!( r#" - INSERT INTO user_stack (id, stack_id, user_id, name, body, created_at, updated_at) - VALUES ($1, $2, $3, $4, $5, $6, $7) + INSERT INTO user_stack (stack_id, user_id, name, body, created_at, updated_at) + VALUES ($1, $2, $3, $4, $5, $6) RETURNING id; "#, - 0_i32, - stack.stack_id, - stack.user_id, - stack.name, - stack.body, - stack.created_at, - stack.updated_at + Uuid::new_v4(), + user_id, + stack_name, + body, + Utc::now(), + Utc::now(), ) .fetch_one(pool.get_ref()) .instrument(query_span) From aa9aadaa98d860dee9c700895e3376acbb7163c5 Mon Sep 17 00:00:00 2001 From: vsilent Date: Mon, 20 Nov 2023 11:54:11 +0200 Subject: [PATCH 16/18] response builder --- custom-stack-payload-6.json | 2 +- custom-stack-payload-7.json | 2 +- custom-stack-payload-8.json | 2 +- src/forms/stack.rs | 236 ++++++++++++++--------------------- src/helpers/json.rs | 228 ++++++++++++++++++--------------- src/helpers/stack/builder.rs | 81 ++++-------- src/models/rating.rs | 2 +- src/routes/client/add.rs | 22 ++-- src/routes/rating/add.rs | 22 ++-- src/routes/rating/get.rs | 30 ++--- src/routes/stack/add.rs | 13 +- src/routes/stack/compose.rs | 31 ++--- src/routes/stack/deploy.rs | 21 +--- src/routes/stack/get.rs | 30 ++--- 14 files changed, 306 insertions(+), 416 deletions(-) diff --git a/custom-stack-payload-6.json b/custom-stack-payload-6.json index 97e422a..61dedb9 100644 --- a/custom-stack-payload-6.json +++ b/custom-stack-payload-6.json @@ -1 +1 @@ -{"commonDomain":"test.app", "domainList":{}, "region":"fsn1", "zone":null, "server":"cx21", "os":"ubuntu-20.04", "ssl":"letsencrypt", "vars":[], "integrated_features":[],"extended_features":[],"subscriptions":["stack_migration"],"save_token":false,"cloud_token":"r6LAjqrynVt7pUwctVkzBlJmKjLOCxJIWjZFMLTkPYCCB4rsgphhEVhiL4DuO757","provider":"htz","stack_code":"another-bot8","selected_plan":"plan-individual-monthly","custom":{"web":[{"name":"Smarty Bot","code":"smarty-bot","domain":"smartybot.xyz","sharedPorts":["8000"],"versions":[],"custom":true,"type":"web","main":true,"_id":"lltkpq6p347kystct","dockerhub_user":"trydirect","dockerhub_name":"smarty-bot","url_app":"smartybot.xyz","url_git":"https://github.com/vsilent/smarty.git","disk_size":"1Gb","ram_size":"1Gb","cpu":1}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"

Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0.6,"ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","versions":[{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}],"domain":"","sharedPorts":["9000"],"main":true,"version":{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}}],"service":[{"_etag":null,"_id":230,"_created":"2023-05-24T12:51:52.108972","_updated":"2023-08-04T12:18:34.670194","name":"pgrst","code":"pgrst","role":null,"type":"service","default":null,"popularity":null,"descr":null,"ports":null,"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":null,"category_id":null,"parent_app_id":null,"full_description":null,"description":"

PostgREST description

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":1,"ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"pgrst","versions":[{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"},{"_etag":null,"_id":563,"_created":null,"_updated":"2023-05-24T12:52:15.351522","app_id":230,"name":"0.0.5","version":"0.0.5","update_status":"ready_for_testing","tag":"0.0.5"}],"domain":"","sharedPorts":["9999"],"main":true,"version":{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"}}],"servers_count":3,"custom_stack_name":"mysampleproject6","custom_stack_code":"another-bot8","custom_stack_category":["New"],"custom_stack_short_description":"sample short description","custom_stack_description":"stack description","custom_stack_publish":false,"project_name":"Smarty Bot","project_git_url":"https://github.com/vsilent/smarty.git","project_overview":"my product 1","project_description":"my product 1"}} +{"commonDomain":"test.app", "domainList":{}, "region":"fsn1", "zone":null, "server":"cx21", "os":"ubuntu-20.04", "ssl":"letsencrypt", "vars":[], "integrated_features":[],"extended_features":[],"subscriptions":["stack_migration"],"save_token":false,"cloud_token":"r6LAjqrynVt7pUwctVkzBlJmKjLOCxJIWjZFMLTkPYCCB4rsgphhEVhiL4DuO757","provider":"htz","stack_code":"another-bot8","selected_plan":"plan-individual-monthly","custom":{"web":[{"name":"Smarty Bot","code":"smarty-bot","domain":"smartybot.xyz","sharedPorts":["8000"],"versions":[],"custom":true,"type":"web","main":true,"_id":0,"dockerhub_user":"trydirect","dockerhub_name":"smarty-bot","url_app":"smartybot.xyz","url_git":"https://github.com/vsilent/smarty.git","disk_size":"1Gb","ram_size":"1Gb","cpu":1}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"

Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0.6,"ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","versions":[{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}],"domain":"","sharedPorts":["9000"],"main":true,"version":{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}}],"service":[{"_etag":null,"_id":230,"_created":"2023-05-24T12:51:52.108972","_updated":"2023-08-04T12:18:34.670194","name":"pgrst","code":"pgrst","role":null,"type":"service","default":null,"popularity":null,"descr":null,"ports":null,"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":null,"category_id":null,"parent_app_id":null,"full_description":null,"description":"

PostgREST description

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":1,"ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"pgrst","versions":[{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"},{"_etag":null,"_id":563,"_created":null,"_updated":"2023-05-24T12:52:15.351522","app_id":230,"name":"0.0.5","version":"0.0.5","update_status":"ready_for_testing","tag":"0.0.5"}],"domain":"","sharedPorts":["9999"],"main":true,"version":{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"}}],"servers_count":3,"custom_stack_name":"mysampleproject6","custom_stack_code":"another-bot8","custom_stack_category":["New"],"custom_stack_short_description":"sample short description","custom_stack_description":"stack description","custom_stack_publish":false,"project_name":"Smarty Bot","project_git_url":"https://github.com/vsilent/smarty.git","project_overview":"my product 1","project_description":"my product 1"}} diff --git a/custom-stack-payload-7.json b/custom-stack-payload-7.json index 61dedb9..1fbfbbd 100644 --- a/custom-stack-payload-7.json +++ b/custom-stack-payload-7.json @@ -1 +1 @@ -{"commonDomain":"test.app", "domainList":{}, "region":"fsn1", "zone":null, "server":"cx21", "os":"ubuntu-20.04", "ssl":"letsencrypt", "vars":[], "integrated_features":[],"extended_features":[],"subscriptions":["stack_migration"],"save_token":false,"cloud_token":"r6LAjqrynVt7pUwctVkzBlJmKjLOCxJIWjZFMLTkPYCCB4rsgphhEVhiL4DuO757","provider":"htz","stack_code":"another-bot8","selected_plan":"plan-individual-monthly","custom":{"web":[{"name":"Smarty Bot","code":"smarty-bot","domain":"smartybot.xyz","sharedPorts":["8000"],"versions":[],"custom":true,"type":"web","main":true,"_id":0,"dockerhub_user":"trydirect","dockerhub_name":"smarty-bot","url_app":"smartybot.xyz","url_git":"https://github.com/vsilent/smarty.git","disk_size":"1Gb","ram_size":"1Gb","cpu":1}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"

Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0.6,"ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","versions":[{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}],"domain":"","sharedPorts":["9000"],"main":true,"version":{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}}],"service":[{"_etag":null,"_id":230,"_created":"2023-05-24T12:51:52.108972","_updated":"2023-08-04T12:18:34.670194","name":"pgrst","code":"pgrst","role":null,"type":"service","default":null,"popularity":null,"descr":null,"ports":null,"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":null,"category_id":null,"parent_app_id":null,"full_description":null,"description":"

PostgREST description

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":1,"ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"pgrst","versions":[{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"},{"_etag":null,"_id":563,"_created":null,"_updated":"2023-05-24T12:52:15.351522","app_id":230,"name":"0.0.5","version":"0.0.5","update_status":"ready_for_testing","tag":"0.0.5"}],"domain":"","sharedPorts":["9999"],"main":true,"version":{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"}}],"servers_count":3,"custom_stack_name":"mysampleproject6","custom_stack_code":"another-bot8","custom_stack_category":["New"],"custom_stack_short_description":"sample short description","custom_stack_description":"stack description","custom_stack_publish":false,"project_name":"Smarty Bot","project_git_url":"https://github.com/vsilent/smarty.git","project_overview":"my product 1","project_description":"my product 1"}} +{"commonDomain":"test.app", "domainList":{}, "region":"fsn1", "zone":null, "server":"cx21", "os":"ubuntu-20.04", "ssl":"letsencrypt", "vars":[], "integrated_features":[],"extended_features":[],"subscriptions":["stack_migration"],"save_token":false,"cloud_token":"r6LAjqrynVt7pUwctVkzBlJmKjLOCxJIWjZFMLTkPYCCB4rsgphhEVhiL4DuO757","provider":"htz","stack_code":"another-bot8","selected_plan":"plan-individual-monthly","custom":{"web":[{"name":"Smarty Bot","code":"smarty-bot","domain":"smartybot.xyz","sharedPorts":["8000"],"versions":[],"custom":true,"type":"web","main":true,"_id":0,"dockerhub_user":"trydirect","dockerhub_name":"smarty-bot","url_app":"smartybot.xyz","url_git":"https://github.com/vsilent/smarty.git","disk_size":"1Gb","ram_size":"1Gb","cpu":1}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"

Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0.6,"ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","versions":[{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}],"domain":"","sharedPorts":["9000"],"main":true,"version":{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-03-17T13:46:51.433539","app_id":198,"name":"latest","version":"latest","update_status":"published","tag":"latest"}}],"service":[{"_etag":null,"_id":230,"_created":"2023-05-24T12:51:52.108972","_updated":"2023-08-04T12:18:34.670194","name":"pgrst","code":"pgrst","role":null,"type":"service","default":null,"popularity":null,"descr":null,"ports":null,"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":null,"category_id":null,"parent_app_id":null,"full_description":null,"description":"

PostgREST description

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":1,"ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"pgrst","versions":[{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"},{"_etag":null,"_id":563,"_created":null,"_updated":"2023-05-24T12:52:15.351522","app_id":230,"name":"0.0.5","version":"0.0.5","update_status":"ready_for_testing","tag":"0.0.5"}],"domain":"","sharedPorts":["9999"],"main":true,"version":{"_etag":"566","_id":566,"_created":"2023-08-15T12:10:44","_updated":"2023-08-15T12:10:44.905249","app_id":230,"name":"PostgreSQL","version":"15_4","update_status":"ready_for_testing","tag":"unstable"}}],"servers_count":3,"custom_stack_name":"mysampleproject7","custom_stack_code":"another-bot9","custom_stack_category":["New"],"custom_stack_short_description":"sample short description","custom_stack_description":"stack description","custom_stack_publish":false,"project_name":"Smarty Bot","project_git_url":"https://github.com/vsilent/smarty.git","project_overview":"my product 1","project_description":"my product 1"}} diff --git a/custom-stack-payload-8.json b/custom-stack-payload-8.json index 49d2786..7a55fc9 100644 --- a/custom-stack-payload-8.json +++ b/custom-stack-payload-8.json @@ -1 +1 @@ -{"commonDomain":"","domainList":{},"region":"fsn1","zone":null,"server":"cpx11","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":false,"disk_type":"pd-standart","cloud_token":"****","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"_etag":null,"_id":233,"_created":"2023-07-19T06:38:57.608807","_updated":"2023-08-15T11:12:14.921797","name":"FastAPI","code":"fastapi","role":null,"type":"web","default":true,"popularity":null,"descr":null,"ports":{"public":["5050","8000","8080"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":2500,"height":2500,"image":"8d1ba06d-04e2-4523-879e-2846c86a10d8.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":null,"plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0.1,"ram_size":"0.2Gb","disk_size":"0.2Gb","dockerhub_image":"fastapi","form":null,"versions":[{"_etag":null,"_id":587,"_created":null,"_updated":"2023-07-20T12:51:20.321999","app_id":233,"name":"0.0.1","version":"0.0.1","update_status":"ready_for_testing","tag":"0.0.1"},{"_etag":null,"_id":590,"_created":null,"_updated":"2023-07-20T08:36:49.651219","app_id":233,"name":"0.0.1","version":"0.0.1","update_status":"ready_for_testing","tag":"latest"},{"_etag":null,"_id":589,"_created":null,"_updated":"2023-07-20T08:36:55.200575","app_id":233,"name":"0.0.1","version":"0.0.1","update_status":"ready_for_testing","tag":"unstable"},{"_etag":"591","_id":591,"_created":null,"_updated":"2023-08-15T08:17:40.226186","app_id":233,"name":"Fastapi","version":"0.100.0","update_status":"published","tag":"stable"}],"domain":"fastapi.test","sharedPorts":["8000"],"main":true,"version":{"_etag":"591","_id":591,"_created":null,"_updated":"2023-08-15T08:17:40.226186","app_id":233,"name":"Fastapi","version":"0.100.0","update_status":"published","tag":"stable"}}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"

Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)

","plan_type":null,"ansible_var":null,"repo_Show more +{"commonDomain":"","domainList":{},"region":"fsn1","zone":null,"server":"cpx11","os":"ubuntu-20.04","ssl":"letsencrypt","vars":[],"integrated_features":[],"extended_features":[],"subscriptions":[],"form_app":[],"save_token":false,"disk_type":"pd-standart","cloud_token":"****","provider":"htz","stack_code":"custom-stack","selected_plan":"plan-individual-monthly","custom":{"web":[{"_etag":null,"_id":233,"_created":"2023-07-19T06:38:57.608807","_updated":"2023-08-15T11:12:14.921797","name":"FastAPI","code":"fastapi","role":null,"type":"web","default":true,"popularity":null,"descr":null,"ports":{"public":["5050","8000","8080"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":2500,"height":2500,"image":"8d1ba06d-04e2-4523-879e-2846c86a10d8.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":null,"plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0.1,"ram_size":"0.2Gb","disk_size":"0.2Gb","dockerhub_image":"fastapi","form":null,"versions":[{"_etag":null,"_id":587,"_created":null,"_updated":"2023-07-20T12:51:20.321999","app_id":233,"name":"0.0.1","version":"0.0.1","update_status":"ready_for_testing","tag":"0.0.1"},{"_etag":null,"_id":590,"_created":null,"_updated":"2023-07-20T08:36:49.651219","app_id":233,"name":"0.0.1","version":"0.0.1","update_status":"ready_for_testing","tag":"latest"},{"_etag":null,"_id":589,"_created":null,"_updated":"2023-07-20T08:36:55.200575","app_id":233,"name":"0.0.1","version":"0.0.1","update_status":"ready_for_testing","tag":"unstable"},{"_etag":"591","_id":591,"_created":null,"_updated":"2023-08-15T08:17:40.226186","app_id":233,"name":"Fastapi","version":"0.100.0","update_status":"published","tag":"stable"}],"domain":"fastapi.test","sharedPorts":["8000"],"main":true,"version":{"_etag":"591","_id":591,"_created":null,"_updated":"2023-08-15T08:17:40.226186","app_id":233,"name":"Fastapi","version":"0.100.0","update_status":"published","tag":"stable"}}],"feature":[{"_etag":null,"_id":198,"_created":"2022-04-27T14:10:27.280327","_updated":"2023-08-03T08:24:18.958721","name":"Portainer CE Feature","code":"portainer_ce_feature","role":["portainer-ce-feature"],"type":"feature","default":null,"popularity":null,"descr":null,"ports":{"public":["9000","8000"]},"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":1138,"height":1138,"image":"08589075-44e6-430e-98a5-f9dcf711e054.svg"},"dark":{}},"category_id":2,"parent_app_id":null,"full_description":null,"description":"

Portainer is a lightweight management UI which allows you to easily manage your different Docker environments (Docker hosts or Swarm clusters)

","plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0.6,"ram_size":"1Gb","disk_size":"1Gb","dockerhub_image":"portainer-ce-feature","form":null,"versions":[{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-09-07T07:28:51.18965","app_id":198,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"}],"domain":"","sharedPorts":["9000"],"main":false,"version":{"_etag":null,"_id":456,"_created":"2022-04-25T12:44:30.964547","_updated":"2023-09-07T07:28:51.18965","app_id":198,"name":"2.18.4","version":"2.18.4","update_status":"published","tag":"2.18.4"}}],"service":[{"_etag":null,"_id":24,"_created":"2020-06-19T13:07:24.228389","_updated":"2023-08-08T10:34:13.4985","name":"PostgreSQL","code":"postgres","role":[],"type":"service","default":null,"popularity":null,"descr":null,"ports":null,"commercial":null,"subscription":null,"autodeploy":null,"suggested":null,"dependency":null,"avoid_render":null,"price":null,"icon":{"light":{"width":576,"height":594,"image":"fd23f54c-e250-4228-8d56-7e5d93ffb925.svg"},"dark":{}},"category_id":null,"parent_app_id":null,"full_description":null,"description":null,"plan_type":null,"ansible_var":null,"repo_dir":null,"cpu":0,"ram_size":null,"disk_size":null,"dockerhub_image":"postgres","form":null,"versions":[{"_etag":null,"_id":458,"_created":"2022-10-20T07:57:05.88997","_updated":"2023-04-05T07:24:39.637749","app_id":24,"name":"15","version":"15","update_status":"published","tag":"15"},{"_etag":null,"_id":288,"_created":"2022-10-20T07:56:16.160116","_updated":"2023-03-17T13:46:51.433539","app_id":24,"name":"10.22","version":"10.22","update_status":"published","tag":"10.22"},{"_etag":null,"_id":303,"_created":"2022-10-20T07:57:24.710286","_updated":"2023-03-17T13:46:51.433539","app_id":24,"name":"13.8","version":"13.8","update_status":"published","tag":"13.8"},{"_etag":null,"_id":266,"_created":"2022-10-20T07:56:32.360852","_updated":"2023-04-05T06:49:31.782132","app_id":24,"name":"11","version":"11","update_status":"published","tag":"11"},{"_etag":null,"_id":267,"_created":"2022-10-20T07:57:35.552085","_updated":"2023-03-17T13:46:51.433539","app_id":24,"name":"12.12","version":"12.12","update_status":"published","tag":"12.12"},{"_etag":null,"_id":38,"_created":"2020-06-19T13:07:24.258724","_updated":"2022-10-20T07:58:06.882602","app_id":24,"name":"14.5","version":"14.5","update_status":"published","tag":"14.5"},{"_etag":null,"_id":564,"_created":null,"_updated":"2023-05-24T12:55:57.894215","app_id":24,"name":"0.0.5","version":"0.0.5","update_status":"ready_for_testing","tag":"0.0.5"},{"_etag":null,"_id":596,"_created":null,"_updated":"2023-08-09T11:00:33.004267","app_id":24,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"}],"domain":"","sharedPorts":["6372"],"main":false,"version":{"_etag":null,"_id":596,"_created":null,"_updated":"2023-08-09T11:00:33.004267","app_id":24,"name":"Postgres","version":"15.1","update_status":"published","tag":"15.1"}}],"servers_count":3,"project_name":"FastAPI example","custom_stack_code":"fastapi-example","project_git_url":"https://github.com/trydirect/fastapi.git"}} diff --git a/src/forms/stack.rs b/src/forms/stack.rs index 541412c..78a0847 100644 --- a/src/forms/stack.rs +++ b/src/forms/stack.rs @@ -1,6 +1,66 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use serde_valid::Validate; +use std::fmt; + + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)] +pub struct Role { + pub role: Option>, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)] +pub struct Requirements { + #[validate(minimum=0.1)] + pub cpu: Option, + #[validate(min_length=1)] + #[validate(max_length=10)] + #[validate(pattern = r"^\d+G$")] + #[serde(rename = "disk_size")] + pub disk_size: Option, + #[serde(rename = "ram_size")] + #[validate(min_length=1)] + #[validate(max_length=10)] + #[validate(pattern = r"^\d+G$")] + pub ram_size: Option, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)] +pub struct Ports { + #[serde(rename(deserialize = "sharedPorts"))] + #[serde(rename(serialize = "shared_ports"))] + #[serde(alias = "shared_ports")] + pub shared_ports: Option>, + pub ports: Option>, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)] +pub struct DockerImage { + pub dockerhub_user: Option, + pub dockerhub_name: Option, + pub dockerhub_image: Option, +} + +impl fmt::Display for DockerImage +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let tag = "latest"; + + let dim = self.dockerhub_image.clone() + .unwrap_or("".to_string()); + write!(f, "{}/{}:{}", self.dockerhub_user.clone() + .unwrap_or("trydirect".to_string()).clone(), + self.dockerhub_name.clone().unwrap_or(dim), tag + ) + } +} + +impl AsRef for App { + fn as_ref(&self) -> &DockerImage { + &self.docker_image + } +} + #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)] pub struct StackForm { @@ -33,7 +93,6 @@ pub struct StackPayload { pub(crate) id: Option, pub(crate) user_token: Option, pub(crate) user_email: Option, - pub(crate) installation_id: Option, #[serde(rename= "commonDomain")] pub common_domain: String, pub domain_list: Option, @@ -55,7 +114,6 @@ pub struct StackPayload { #[serde(rename = "cloud_token")] pub cloud_token: String, pub provider: String, - #[serde(rename = "stack_code")] pub stack_code: String, #[serde(rename = "selected_plan")] pub selected_plan: String, @@ -103,54 +161,8 @@ pub struct Custom { pub project_description: Option, } - #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)] -#[serde(rename_all = "camelCase")] -pub struct Web { - pub name: String, - pub code: String, - pub domain: Option, - //#[serde(rename = "shared_ports")] - // #[serde(rename= "sharedPorts")] - #[serde(rename(deserialize = "sharedPorts"))] - #[serde(rename(serialize = "shared_ports"))] - #[serde(alias = "shared_ports")] - pub shared_ports: Option>, - pub versions: Option>, - pub custom: Option, - #[serde(rename = "type")] - pub type_field: String, - pub main: bool, - #[serde(rename = "_id")] - pub id: u32, - #[serde(rename = "dockerhub_user")] - pub dockerhub_user: Option, - #[serde(rename = "dockerhub_name")] - pub dockerhub_name: Option, - // when no docker repo specified by user - // use default TD image - pub dockerhub_image: Option, - #[serde(rename = "url_app")] - pub url_app: Option, - #[serde(rename = "url_git")] - pub url_git: Option, - #[validate(min_length=1)] - #[validate(max_length=10)] - #[validate(pattern = r"^\d+G$")] - #[serde(rename = "disk_size")] - pub disk_size: String, - #[serde(rename = "ram_size")] - #[validate(min_length=1)] - #[validate(max_length=10)] - #[validate(pattern = r"^\d+G$")] - pub ram_size: Option, - #[validate(minimum=0.1)] - pub cpu: Option, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)] -#[serde(rename_all = "camelCase")] -pub struct Feature { +pub struct App { #[serde(rename = "_etag")] pub etag: Option, #[serde(rename = "_id")] @@ -161,62 +173,60 @@ pub struct Feature { pub updated: Option, pub name: String, pub code: String, - pub role: Vec, #[serde(rename = "type")] pub type_field: String, + #[serde(flatten)] + pub role: Role, pub default: Option, - pub popularity: Option, - pub descr: Option, + #[serde(flatten)] pub ports: Option, + pub versions: Option>, + #[serde(flatten)] + pub docker_image: DockerImage, + #[serde(flatten)] + pub requirements: Requirements, + pub popularity: Option, pub commercial: Option, pub subscription: Option, pub autodeploy: Option, pub suggested: Option, pub dependency: Option, - #[serde(rename = "avoid_render")] pub avoid_render: Option, pub price: Option, pub icon: Option, - #[serde(rename = "category_id")] + pub domain: Option, + pub main: bool, pub category_id: Option, - #[serde(rename = "parent_app_id")] pub parent_app_id: Option, - #[serde(rename = "full_description")] + pub descr: Option, pub full_description: Option, pub description: Option, - #[serde(rename = "plan_type")] pub plan_type: Option, - #[serde(rename = "ansible_var")] pub ansible_var: Option, - #[serde(rename = "repo_dir")] pub repo_dir: Option, - pub cpu: Option, - #[validate(min_length=1)] - #[validate(max_length=10)] - #[validate(pattern = r"^\d+G$")] - pub ram_size: Option, - #[validate(min_length=1)] - #[serde(rename = "disk_size")] - pub disk_size: Option, - #[serde(rename = "dockerhub_image")] - pub dockerhub_image: Option, - pub versions: Option>, - pub domain: Option, - // #[serde(rename = "shared_ports")] - // #[serde(rename= "sharedPorts")] - #[serde(rename(deserialize = "sharedPorts"))] - #[serde(rename(serialize = "shared_ports"))] - #[serde(alias = "shared_ports")] - pub shared_ports: Option>, - pub main: bool, + pub url_app: Option, + pub url_git: Option, } -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Ports { - pub public: Vec, +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)] +pub struct Web { + #[serde(flatten)] + pub app: App, + pub custom: Option, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)] +pub struct Feature { + #[serde(flatten)] + pub app: App, } +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)] +#[serde(rename_all = "camelCase")] +pub struct Service { + #[serde(flatten)] + pub(crate) app: App, +} #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -258,66 +268,4 @@ pub struct Version { pub tag: String, } -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Validate)] -#[serde(rename_all = "camelCase")] -pub struct Service { - #[serde(rename = "_etag")] - pub etag: Option, - #[serde(rename = "_id")] - pub id: u32, - #[serde(rename = "_created")] - pub created: Option, - #[serde(rename = "_updated")] - pub updated: Option, - pub name: String, - pub code: String, - pub role: Option>, - #[serde(rename = "type")] - pub type_field: String, - pub default: Option, - pub popularity: Option, - pub descr: Option, - pub ports: Option, - pub commercial: Option, - pub subscription: Option, - pub autodeploy: Option, - pub suggested: Option, - pub dependency: Option, - #[serde(rename = "avoid_render")] - pub avoid_render: Option, - pub price: Option, - pub icon: Option, - #[serde(rename = "category_id")] - pub category_id: Option, - #[serde(rename = "parent_app_id")] - pub parent_app_id: Option, - #[serde(rename = "full_description")] - pub full_description: Option, - pub description: Option, - #[serde(rename = "plan_type")] - pub plan_type: Option, - #[serde(rename = "ansible_var")] - pub ansible_var: Option, - #[serde(rename = "repo_dir")] - pub repo_dir: Option, - pub cpu: Option, - #[validate(min_length=1)] - #[validate(max_length=10)] - #[validate(pattern = r"^\d+G$")] - pub ram_size: Option, - #[serde(rename = "disk_size")] - #[validate(min_length=1)] - pub disk_size: Option, - #[serde(rename = "dockerhub_image")] - pub dockerhub_image: Option, - pub versions: Option>, - pub domain: String, - // #[serde(rename = "shared_ports")] - // #[serde(rename= "sharedPorts")] - #[serde(rename(deserialize = "sharedPorts"))] - #[serde(rename(serialize = "shared_ports"))] - #[serde(alias = "shared_ports")] - pub shared_ports: Option>, - pub main: bool, -} diff --git a/src/helpers/json.rs b/src/helpers/json.rs index 6321a06..52d40db 100644 --- a/src/helpers/json.rs +++ b/src/helpers/json.rs @@ -1,13 +1,12 @@ use actix_web::{Responder, Result}; +use actix_web::error::{ErrorBadRequest, ErrorConflict, ErrorNotFound, ErrorInternalServerError}; use serde_derive::Serialize; -use actix_web::web; -use actix_web::web::JsonBody::Error; +use actix_web::web::Json; +use actix_web::Error; #[derive(Serialize)] pub(crate) struct JsonResponse { - pub(crate) status: String, pub(crate) message: String, - pub(crate) code: u32, pub(crate) id: Option, pub(crate) item: Option, pub(crate) list: Option> @@ -18,58 +17,96 @@ pub(crate) struct JsonResponse { pub struct JsonResponseBuilder where T: serde::Serialize + Default { - status: String, - message: String, - code: u32, id: Option, item: Option, list: Option> } impl JsonResponseBuilder -where T: serde::Serialize + Default + where T: serde::Serialize + Default { - pub(crate) fn new() -> Self { - Self::default() - } + pub(crate) fn new() -> Self { + Self::default() + } - fn set_item(mut self, item:T) -> Self { + pub(crate) fn set_item(mut self, item:T) -> Self { self.item = Some(item); self } + pub(crate) fn set_id(mut self, id:i32) -> Self { + self.id = Some(id); + self + } pub(crate) fn set_list(mut self, list:Vec) -> Self { self.list = Some(list); self } - pub(crate) fn ok(self) -> Result { + pub(crate) fn ok(self, msg: String) -> Result>, Error> { - Ok(web::Json( + Ok(Json( JsonResponse { - status: self.status, - message: self.message, - code: self.code, + message: msg, id: self.id, item: self.item, list: self.list, } )) + } - pub(crate) fn err(self) -> Result { + pub(crate) fn err(self, msg: String) -> Result>, Error> { - Ok(web::Json( - JsonResponse { - status: self.status, - message: self.message, - code: self.code, - id: self.id, - item: self.item, - list: self.list, - } - )) + let json_response = JsonResponse { + message: msg, + id: self.id, + item: self.item, + list: self.list + }; + + Err(ErrorBadRequest( + serde_json::to_string(&json_response).unwrap())) + } + + pub(crate) fn not_found(self, msg: String) -> Result>, Error> { + + let json_response = JsonResponse { + message: msg, + id: self.id, + item: self.item, + list: self.list + }; + + Err(ErrorNotFound( + serde_json::to_string(&json_response).unwrap())) + } + + pub(crate) fn internal_error(self, msg: String) -> Result>, Error> { + + let json_response = JsonResponse { + message: msg, + id: self.id, + item: self.item, + list: self.list + }; + + Err(ErrorInternalServerError( + serde_json::to_string(&json_response).unwrap())) + } + + pub(crate) fn conflict(self, msg: String) -> Result>, Error> { + + let json_response = JsonResponse { + message: msg, + id: self.id, + item: self.item, + list: self.list + }; + + Err(ErrorConflict( + serde_json::to_string(&json_response).unwrap())) } } @@ -81,94 +118,87 @@ impl From for JsonResponseBuilder } impl From> for JsonResponseBuilder -where T: serde::Serialize + Default { + where T: serde::Serialize + Default { fn from(value: Vec) -> Self { JsonResponseBuilder::default().set_list(value) } } impl JsonResponse +where T: serde::Serialize + Default { - pub(crate) fn new(status: String, - message: String, - code: u32, + pub fn build() -> JsonResponseBuilder + { + JsonResponseBuilder::default() + } + pub(crate) fn new(message: String, id: Option, item:Option, list: Option>) -> Self { tracing::debug!("Executed.."); JsonResponse { - status, message, - code, id, item, list, } } - pub(crate) fn ok(id: i32, message: &str) -> JsonResponse { - - let msg = if !message.trim().is_empty() { - message.to_string() - } - else{ - String::from("Success") - }; - - JsonResponse { - status: "OK".to_string(), - message: msg, - code: 200, - id: Some(id), - item: None, - list: None, - } - } - - pub(crate) fn not_found() -> Self { - JsonResponse { - status: "Error".to_string(), - code: 404, - id: None, - item: None, - message: format!("Object not found"), - list: None, - } - } - - pub(crate) fn internal_error(message: &str) -> Self { - - let msg = if !message.trim().is_empty() { - message.to_string() - } - else{ - String::from("Internal error") - }; - JsonResponse { - status: "Error".to_string(), - code: 500, - id: None, - item: None, - message: msg, - list: None, - } - } - - pub(crate) fn not_valid(message: &str) -> Self { - - let msg = if !message.trim().is_empty() { - message.to_string() - } - else{ - String::from("Validation error") - }; - JsonResponse { - status: "Error".to_string(), - code: 400, - id: None, - item: None, - message: msg, - list: None, - } - } + // pub(crate) fn ok(id: i32, message: &str) -> JsonResponse { + // + // let msg = if !message.trim().is_empty() { + // message.to_string() + // } + // else{ + // String::from("Success") + // }; + // + // JsonResponse { + // message: msg, + // id: Some(id), + // item: None, + // list: None, + // } + // } + + // pub(crate) fn not_found() -> Self { + // JsonResponse { + // id: None, + // item: None, + // message: format!("Object not found"), + // list: None, + // } + // } + + // pub(crate) fn internal_error(message: &str) -> Self { + // + // let msg = if !message.trim().is_empty() { + // message.to_string() + // } + // else{ + // String::from("Internal error") + // }; + // JsonResponse { + // id: None, + // item: None, + // message: msg, + // list: None, + // } + // } + // + // pub(crate) fn not_valid(message: &str) -> Self { + // + // let msg = if !message.trim().is_empty() { + // message.to_string() + // } + // else{ + // String::from("Validation error") + // }; + // JsonResponse { + // id: None, + // item: None, + // message: msg, + // list: None, + // } + // } } \ No newline at end of file diff --git a/src/helpers/stack/builder.rs b/src/helpers/stack/builder.rs index 3986650..c319d8a 100644 --- a/src/helpers/stack/builder.rs +++ b/src/helpers/stack/builder.rs @@ -7,18 +7,10 @@ use crate::helpers::stack::dctypes::{ Services }; use serde_yaml; -use crate::forms; -use crate::forms::{ - StackForm, - Web, - Feature -}; +use crate::forms::{StackForm, stack}; use crate::models::stack::Stack; - #[derive(Clone, Debug)] -struct Config { - -} +struct Config {} impl Default for Config { fn default() -> Self { @@ -33,26 +25,13 @@ pub struct DcBuilder { pub(crate) stack: Stack } -impl TryInto> for Web { +impl TryInto> for stack::Ports { type Error = String; fn try_into(self) -> Result, Self::Error> { convert_shared_ports(self.shared_ports.clone().unwrap()) } } -impl TryInto> for &Feature { - type Error = String; - fn try_into(self) -> Result, Self::Error> { - convert_shared_ports(self.shared_ports.clone().unwrap()) - } -} - -impl TryInto> for &forms::stack::Service { - type Error = String; - fn try_into(self) -> Result, Self::Error> { - convert_shared_ports(self.shared_ports.clone().unwrap()) - } -} fn convert_shared_ports(ports: Vec) -> Result, String> { let mut _ports: Vec = vec![]; @@ -78,6 +57,7 @@ impl DcBuilder { } } + pub fn build(&self) -> Option { tracing::debug!("Start build docker compose from {:?}", &self.stack.body); @@ -87,25 +67,16 @@ impl DcBuilder { Ok(apps) => { println!("stack item {:?}", apps.custom.web); - for app in apps.custom.web { - let tag = "latest"; - let dim = app.dockerhub_image.clone().unwrap_or("".to_string()); - let img= format!("{}/{}:{}", - app.dockerhub_user.clone() - .unwrap_or("trydirect".to_string()).clone(), - app.dockerhub_name.clone().unwrap_or(dim), - tag - ); - let code = app.code.clone().to_owned(); - + for app_type in apps.custom.web { + let code = app_type.app.code.clone().to_owned(); let mut service = Service { - image: Some(img.to_string()), + image: Some(app_type.app.docker_image.to_string()), ..Default::default() }; - if let Some(ports) = &app.shared_ports { - if !ports.is_empty() { - service.ports = Ports::Long(app.try_into().unwrap()) + if let Some(ports) = &app_type.app.ports { + if !ports.shared_ports.clone()?.is_empty() { + service.ports = Ports::Long(app_type.app.ports?.try_into().unwrap()) } } @@ -116,22 +87,20 @@ impl DcBuilder { ); } - if let Some(srvs) = &apps.custom.service { + if let Some(srvs) = apps.custom.service { if !srvs.is_empty() { - for app in srvs { - let code = app.code.to_owned(); - let tag = "latest"; - + for app_type in srvs { + let code = app_type.app.code.to_owned(); let mut service = Service { - image: Some(app.dockerhub_image.as_ref().unwrap().to_owned()), + image: Some(app_type.app.docker_image.to_string()), ..Default::default() }; - if let Some(ports) = &app.shared_ports { - if !ports.is_empty() { - service.ports = Ports::Long(app.try_into().unwrap()) + if let Some(ports) = &app_type.app.ports { + if !ports.shared_ports.clone()?.is_empty() { + service.ports = Ports::Long(app_type.app.ports?.try_into().unwrap()) } } service.restart = Some("always".to_owned()); @@ -142,20 +111,21 @@ impl DcBuilder { } } } - if let Some(features) = &apps.custom.feature { + if let Some(features) = apps.custom.feature { if !features.is_empty() { - for app in features { - let code = app.code.to_owned(); + for app_type in features { + let code = app_type.app.code.to_owned(); let mut service = Service { - image: Some(app.dockerhub_image.as_ref().unwrap().to_owned()), + // image: Some(app.dockerhub_image.as_ref().unwrap().to_owned()), + image: Some(app_type.app.docker_image.to_string()), ..Default::default() }; - if let Some(ports) = &app.shared_ports { - if !ports.is_empty() { - service.ports = Ports::Long(app.try_into().unwrap()) + if let Some(ports) = &app_type.app.ports { + if !ports.shared_ports.clone()?.is_empty() { + service.ports = Ports::Long(app_type.app.ports?.try_into().unwrap()) } } service.restart = Some("always".to_owned()); @@ -168,6 +138,7 @@ impl DcBuilder { } } Err(e) => { + tracing::debug!("Unpack stack form {:?}", e); () } } diff --git a/src/models/rating.rs b/src/models/rating.rs index 9a3f00b..dedf8fd 100644 --- a/src/models/rating.rs +++ b/src/models/rating.rs @@ -16,7 +16,7 @@ pub struct Product { pub updated_at: DateTime, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, Default)] pub struct Rating { pub id: i32, pub user_id: String, // external user_id, 100, taken using token (middleware?) diff --git a/src/routes/client/add.rs b/src/routes/client/add.rs index ae822f8..8b7eded 100644 --- a/src/routes/client/add.rs +++ b/src/routes/client/add.rs @@ -39,14 +39,12 @@ pub async fn add_handler( client_count ); - return Ok(web::Json(JsonResponse::not_valid( - "Too many clients already created")) - ); + return JsonResponse::build().err("Too many clients created".to_owned()); } } Err(e) => { tracing::error!("Failed to execute query: {:?}", e); - return Ok(web::Json(JsonResponse::internal_error("Failed to insert"))); + return JsonResponse::build().internal_error("Failed to insert".to_owned()); } }; @@ -72,18 +70,16 @@ pub async fn add_handler( Ok(result) => { tracing::info!("New client {} have been saved to database", result.id); client.id = result.id; - Ok(web::Json(JsonResponse::new( - "success".to_string(), - "".to_string(), - 200, - Some(client.id), - Some(client), - None - ))) + + return JsonResponse::build() + .set_id(client.id) + .set_item(Some(client)) + .ok("OK".to_owned()); } Err(e) => { tracing::error!("Failed to execute query: {:?}", e); - Ok(web::Json(JsonResponse::internal_error("Failed to insert"))) + let err = format!("Failed to insert. {}", e); + return JsonResponse::build().err(err); } } } diff --git a/src/routes/rating/add.rs b/src/routes/rating/add.rs index 382b564..eab47d1 100644 --- a/src/routes/rating/add.rs +++ b/src/routes/rating/add.rs @@ -36,7 +36,8 @@ pub async fn add_handler( } Err(e) => { tracing::error!("Failed to fetch product: {:?}, error: {:?}", form.obj_id, e); - return Ok(web::Json(JsonResponse::::not_found())); + return JsonResponse::::build() + .err(format!("Object not found {}", form.obj_id)); } }; @@ -59,20 +60,12 @@ pub async fn add_handler( form.obj_id, form.category ); - - return Ok(web::Json(JsonResponse::::new( - "Error".to_string(), - "Already rated".to_string(), - 409, - Some(record.id), - None, - None - ))); + return JsonResponse::build().conflict("Already rated".to_owned()); } Err(sqlx::Error::RowNotFound) => {} Err(e) => { tracing::error!("Failed to fetch rating, error: {:?}", e); - return Ok(web::Json(JsonResponse::::internal_error(""))); + return JsonResponse::build().err(format!("Internal Server Error")); } } @@ -99,11 +92,14 @@ pub async fn add_handler( { Ok(result) => { tracing::info!("New rating {} have been saved to database", result.id); - return Ok(web::Json(JsonResponse::::ok(result.id, "Saved"))); + + return JsonResponse::build() + .set_id(result.id) + .ok("Saved".to_owned()); } Err(e) => { tracing::error!("Failed to execute query: {:?}", e); - return Ok(web::Json(JsonResponse::internal_error("Failed to insert"))); + return JsonResponse::build().internal_error("Failed to insert".to_owned()); } } } diff --git a/src/routes/rating/get.rs b/src/routes/rating/get.rs index 19d9399..86002f8 100644 --- a/src/routes/rating/get.rs +++ b/src/routes/rating/get.rs @@ -1,9 +1,9 @@ use crate::models; use actix_web::{get, web, Responder, Result}; +use serde_derive::Serialize; use sqlx::PgPool; use tracing::Instrument; use crate::helpers::JsonResponse; -use crate::models::user::User; // workflow // add, update, list, get(user_id), ACL, @@ -30,22 +30,15 @@ pub async fn get_handler( .await { Ok(rating) => { - tracing::info!("rating found: {:?}", rating.id,); - return Ok(web::Json(JsonResponse::new( - "Success".to_string(), - "Rating found".to_string(), - 200, - Some(rating.id), - Some(rating), - None - ))); + tracing::info!("rating found: {:?}", rating.id); + return JsonResponse::build().set_item(Some(rating)).ok("OK".to_string()); } Err(sqlx::Error::RowNotFound) => { - return Ok(web::Json(JsonResponse::not_found())); + return JsonResponse::build().err("Not Found".to_string()); } Err(e) => { tracing::error!("Failed to fetch rating, error: {:?}", e); - return Ok(web::Json(JsonResponse::internal_error(""))); + return JsonResponse::build().err("Internal Server Error".to_string()); } } } @@ -70,21 +63,14 @@ pub async fn list_handler( { Ok(rating) => { tracing::info!("Ratings found: {:?}", rating.len()); - return Ok(web::Json(JsonResponse::new( - "Success".to_string(), - "".to_string(), - 200, - None, - None, - Some(rating), - ))); + return JsonResponse::build().set_list(rating).ok("OK".to_owned()); } Err(sqlx::Error::RowNotFound) => { - return Ok(web::Json(JsonResponse::not_found())); + return JsonResponse::build().not_found("Not Found".to_owned()); } Err(e) => { tracing::error!("Failed to fetch rating, error: {:?}", e); - return Ok(web::Json(JsonResponse::internal_error(""))); + return JsonResponse::build().internal_error("Internal Server Error".to_owned()); } } } diff --git a/src/routes/stack/add.rs b/src/routes/stack/add.rs index 2ae44a4..841d41b 100644 --- a/src/routes/stack/add.rs +++ b/src/routes/stack/add.rs @@ -1,13 +1,12 @@ use actix_web::{ web, - web::{Bytes, Data, Json}, + web::{Bytes, Data}, Responder, Result, }; use crate::forms::stack::StackForm; use crate::helpers::JsonResponse; use crate::models::user::User; -use crate::models::Stack; use actix_web::post; use chrono::Utc; use serde_json::Value; @@ -33,7 +32,7 @@ pub async fn add( } Err(_err) => { let msg = format!("Invalid data. {:?}", _err); - return Ok(Json(JsonResponse::::not_valid(msg.as_str()))); + return JsonResponse::::build().err("Invalid data".to_owned()); } }; @@ -88,11 +87,11 @@ pub async fn add( "req_id: {} New stack details have been saved to database", request_id ); - Ok(Json(JsonResponse::::ok(record.id, "Object saved"))) + return JsonResponse::build().set_id(record.id).ok("OK".to_owned()); } - Err(err) => { - tracing::error!("req_id: {} Failed to execute query: {:?}", request_id, err); - Ok(Json(JsonResponse::::not_valid("Failed to insert"))) + Err(e) => { + tracing::error!("req_id: {} Failed to execute query: {:?}", request_id, e); + return JsonResponse::build().err("Failed to fetch".to_owned()); } }; } diff --git a/src/routes/stack/compose.rs b/src/routes/stack/compose.rs index 555d7dc..4d19a3f 100644 --- a/src/routes/stack/compose.rs +++ b/src/routes/stack/compose.rs @@ -53,19 +53,15 @@ pub async fn add( let id = stack.id.clone(); let mut dc = DcBuilder::new(stack); let fc = dc.build(); - // tracing::debug!("Docker compose file content {:?}", fc.unwrap()); - return Ok(Json(JsonResponse::new( - "OK".to_owned(), - "Success".to_owned(), - 200, - Some(id), - Some(fc.unwrap()), - None - ))); + tracing::debug!("Docker compose file content {:?}", fc); + return JsonResponse::build() + .set_id(id) + .set_item(fc.unwrap()) + .ok("Success".to_owned()); } None => { - return Ok(Json(JsonResponse::internal_error("Could not generate compose file"))); + return JsonResponse::build().err("Could not generate compose file".to_owned()); } } } @@ -110,19 +106,14 @@ pub async fn admin( let id = stack.id.clone(); let mut dc = DcBuilder::new(stack); let fc = dc.build(); - // tracing::debug!("Docker compose file content {:?}", fc.unwrap()); - return Ok(Json(JsonResponse::new( - "OK".to_owned(), - "Success".to_owned(), - 200, - Some(id), - Some(fc.unwrap()), - None - ))); + // tracing::debug!("Docker compose file content {:?}", fc); + return JsonResponse::build() + .set_id(id) + .set_item(fc.unwrap()).ok("Success".to_owned()); } None => { - return Ok(Json(JsonResponse::internal_error("Could not generate compose file"))); + return JsonResponse::build().err("Could not generate compose file".to_owned()); } } } diff --git a/src/routes/stack/deploy.rs b/src/routes/stack/deploy.rs index aaf7135..45c1b38 100644 --- a/src/routes/stack/deploy.rs +++ b/src/routes/stack/deploy.rs @@ -2,23 +2,21 @@ use std::sync::Arc; use actix_web::{ web, post, - web::{Data, Json}, + web::Data, Responder, Result, }; use crate::models::user::User; use crate::models::stack::Stack; use sqlx::PgPool; use lapin::{ - options::*, publisher_confirm::Confirmation, types::FieldTable, BasicProperties, Connection, + options::*, publisher_confirm::Confirmation, BasicProperties, Connection, ConnectionProperties }; use crate::configuration::Settings; use crate::helpers::JsonResponse; use crate::helpers::stack::builder::DcBuilder; use futures_lite::stream::StreamExt; -use serde::Serialize; -use crate::forms::{StackForm, StackPayload}; - +use crate::forms::StackPayload; #[tracing::instrument(name = "Deploy for every user. Admin endpoint")] @@ -78,7 +76,6 @@ pub async fn add( ).unwrap(); stack_data.id = Some(id); - stack_data.installation_id = None; stack_data.user_token = Some(user.id.clone()); stack_data.user_email = Some(user.email.clone()); stack_data.stack_code = stack_data.custom.custom_stack_code.clone(); @@ -99,18 +96,10 @@ pub async fn add( assert_eq!(confirm, Confirmation::NotRequested); tracing::debug!("Message sent to rabbitmq"); - - Ok(Json(JsonResponse::::new( - "OK".to_owned(), - "Success".to_owned(), - 200, - Some(id), - None, - None - ))) + return JsonResponse::::build().set_id(id).ok("Success".to_owned()); } None => { - Ok(Json(JsonResponse::internal_error("Deployment failed"))) + JsonResponse::build().internal_error("Deployment failed".to_owned()) } } } diff --git a/src/routes/stack/get.rs b/src/routes/stack/get.rs index 24230b9..271d2dc 100644 --- a/src/routes/stack/get.rs +++ b/src/routes/stack/get.rs @@ -30,28 +30,15 @@ pub async fn item( .await { Ok(stack) => { - tracing::info!("stack found: {:?}", stack.id,); - // return Ok(web::Json(JsonResponse::::new( - // "Success".to_string(), - // "".to_string(), - // 200, - // Some(stack.id), - // Some(stack), - // None))); - let response_builder:JsonResponseBuilder = From::from(stack); - // let response: JsonResponse = From::::from(stack).build(); - return response_builder.ok(); + tracing::info!("Stack found: {:?}", stack.id,); + return JsonResponse::build().set_item(Some(stack)).ok("OK".to_owned()); } Err(sqlx::Error::RowNotFound) => { - // return Ok(web::Json(JsonResponse::::not_found())); - let response_builder:JsonResponseBuilder =JsonResponseBuilder::default(); - return response_builder.ok(); + JsonResponse::build().not_found("Record not found".to_owned()) } Err(e) => { tracing::error!("Failed to fetch stack, error: {:?}", e); - // return Ok(web::Json(JsonResponse::::internal_error(""))); - let response_builder:JsonResponseBuilder = JsonResponseBuilder::default(); - return response_builder.ok(); + return JsonResponse::build().internal_error("Could not fetch data".to_owned()); } } } @@ -86,18 +73,15 @@ pub async fn list( .await { Ok(list) => { - let response_builder:JsonResponseBuilder = JsonResponseBuilder::new(); - return response_builder.set_list(list).ok(); + return JsonResponse::build().set_list(list).ok("OK".to_string()); } Err(sqlx::Error::RowNotFound) => { tracing::error!("No stacks found for user: {:?}", &user.id); - let response_builder:JsonResponseBuilder = JsonResponseBuilder::default(); - return response_builder.ok(); + return JsonResponse::build().not_found("No stacks found for user".to_string()) } Err(e) => { tracing::error!("Failed to fetch stack, error: {:?}", e); - let response_builder:JsonResponseBuilder = JsonResponseBuilder::default(); - return response_builder.ok(); + return JsonResponse::build().internal_error("Could not fetch".to_string()); } } } From 0477d1d6ba8110d000ef3c0fc4b3f7a97d81b0eb Mon Sep 17 00:00:00 2001 From: vsilent Date: Mon, 20 Nov 2023 12:02:12 +0200 Subject: [PATCH 17/18] move json testing payloads into tests dir --- custom-stack-payload-2.json => tests/custom-stack-payload-2.json | 0 custom-stack-payload-3.json => tests/custom-stack-payload-3.json | 0 custom-stack-payload-4.json => tests/custom-stack-payload-4.json | 0 custom-stack-payload-5.json => tests/custom-stack-payload-5.json | 0 custom-stack-payload-6.json => tests/custom-stack-payload-6.json | 0 custom-stack-payload-7.json => tests/custom-stack-payload-7.json | 0 custom-stack-payload-8.json => tests/custom-stack-payload-8.json | 0 custom-stack-payload-9.json => tests/custom-stack-payload-9.json | 0 .../custom-stack-payload-singleapp.json | 0 custom-stack-payload.json => tests/custom-stack-payload.json | 0 10 files changed, 0 insertions(+), 0 deletions(-) rename custom-stack-payload-2.json => tests/custom-stack-payload-2.json (100%) rename custom-stack-payload-3.json => tests/custom-stack-payload-3.json (100%) rename custom-stack-payload-4.json => tests/custom-stack-payload-4.json (100%) rename custom-stack-payload-5.json => tests/custom-stack-payload-5.json (100%) rename custom-stack-payload-6.json => tests/custom-stack-payload-6.json (100%) rename custom-stack-payload-7.json => tests/custom-stack-payload-7.json (100%) rename custom-stack-payload-8.json => tests/custom-stack-payload-8.json (100%) rename custom-stack-payload-9.json => tests/custom-stack-payload-9.json (100%) rename custom-stack-payload-singleapp.json => tests/custom-stack-payload-singleapp.json (100%) rename custom-stack-payload.json => tests/custom-stack-payload.json (100%) diff --git a/custom-stack-payload-2.json b/tests/custom-stack-payload-2.json similarity index 100% rename from custom-stack-payload-2.json rename to tests/custom-stack-payload-2.json diff --git a/custom-stack-payload-3.json b/tests/custom-stack-payload-3.json similarity index 100% rename from custom-stack-payload-3.json rename to tests/custom-stack-payload-3.json diff --git a/custom-stack-payload-4.json b/tests/custom-stack-payload-4.json similarity index 100% rename from custom-stack-payload-4.json rename to tests/custom-stack-payload-4.json diff --git a/custom-stack-payload-5.json b/tests/custom-stack-payload-5.json similarity index 100% rename from custom-stack-payload-5.json rename to tests/custom-stack-payload-5.json diff --git a/custom-stack-payload-6.json b/tests/custom-stack-payload-6.json similarity index 100% rename from custom-stack-payload-6.json rename to tests/custom-stack-payload-6.json diff --git a/custom-stack-payload-7.json b/tests/custom-stack-payload-7.json similarity index 100% rename from custom-stack-payload-7.json rename to tests/custom-stack-payload-7.json diff --git a/custom-stack-payload-8.json b/tests/custom-stack-payload-8.json similarity index 100% rename from custom-stack-payload-8.json rename to tests/custom-stack-payload-8.json diff --git a/custom-stack-payload-9.json b/tests/custom-stack-payload-9.json similarity index 100% rename from custom-stack-payload-9.json rename to tests/custom-stack-payload-9.json diff --git a/custom-stack-payload-singleapp.json b/tests/custom-stack-payload-singleapp.json similarity index 100% rename from custom-stack-payload-singleapp.json rename to tests/custom-stack-payload-singleapp.json diff --git a/custom-stack-payload.json b/tests/custom-stack-payload.json similarity index 100% rename from custom-stack-payload.json rename to tests/custom-stack-payload.json From 96a5538bc7b4ba8dcd6fb197db7140de1e8ffe5c Mon Sep 17 00:00:00 2001 From: vsilent Date: Wed, 22 Nov 2023 09:38:03 +0200 Subject: [PATCH 18/18] cleanup --- src/startup.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/startup.rs b/src/startup.rs index c295136..ca705db 100644 --- a/src/startup.rs +++ b/src/startup.rs @@ -114,15 +114,6 @@ pub async fn run( .service(crate::routes::rating::get_handler) .service(crate::routes::rating::list_handler), ) - // .service( - // web::resource("/stack/{id}") - // .route(web::get() - // .to(crate::routes::stack::get)) - // .route(web::post() - // .to(crate::routes::stack::update)) - // .route(web::post() - // .to(crate::routes::stack::add)), - // ) .service( web::scope("/stack") .wrap(HttpAuthentication::bearer(bearer_guard))