diff --git a/examples/server_ordering.rhai b/examples/server_ordering.rhai index d0a6d9a..3c375d1 100644 --- a/examples/server_ordering.rhai +++ b/examples/server_ordering.rhai @@ -17,6 +17,14 @@ let TRANSACTION_ID = "YOUR_TRANSACTION_ID_HERE"; // Example: 2739642 let AUCTION_PRODUCT_ID = 0; // Replace with an actual auction product ID +// Server Number for fetching server addon products or ordering addons. +// Example: 1234567 +let SERVER_NUMBER = 0; // Replace with an actual server number + +// Addon Transaction ID for fetching specific addon transaction details. +// Example: "B20220210-1843193-S33055" +let ADDON_TRANSACTION_ID = "YOUR_ADDON_TRANSACTION_ID_HERE"; + // --- Server Ordering Operations --- // 1. Get all available server products @@ -176,4 +184,66 @@ try { } catch (err) { print("Error ordering auction server: " + err); } +*/ + +// 11. Get all available server addon products +// This section retrieves all available server addon products for a specific server +// and prints them in a human-readable table format. +// Uncomment the following lines and set SERVER_NUMBER to an actual server number to use. +/* +print("\nFetching all available server addon products for server: " + SERVER_NUMBER + "..."); +try { + let available_server_addons = hetzner.get_server_addon_products(SERVER_NUMBER); + available_server_addons.pretty_print(); + print("All available server addon products fetched and displayed."); +} catch (err) { + print("Error fetching all available server addon products: " + err); +} +*/ + +// 12. List all addon transactions from the past 30 days +// This section retrieves all server addon order transactions from the past 30 days. +// Uncomment the following lines to list addon transactions. +/* +print("\nFetching all server addon transactions from the past 30 days..."); +try { + let addon_transactions_last_30 = hetzner.get_server_addon_transactions(); + addon_transactions_last_30.pretty_print(); + print("All addon transactions fetched and displayed."); +} catch (err) { + print("Error fetching addon transactions: " + err); +} +*/ + +// 13. Order a server addon +// This section demonstrates how to order a new server addon. +// Ensure SERVER_NUMBER, PRODUCT_ID, and REASON are set correctly. +// Uncomment the following lines to order a server addon. +/* +print("\nAttempting to order a server addon for server: " + SERVER_NUMBER + " with product ID: " + SERVER_PRODUCT_ID); +try { + let order_addon_builder = new_server_addon_builder(SERVER_NUMBER, SERVER_PRODUCT_ID) + .with_reason("Test order") // Mandatory for some addon types, e.g., "ip_ipv4" + .with_test(true); // Set to 'false' for a real order + + let ordered_addon_transaction = hetzner.order_server_addon(order_addon_builder); + print("Server addon ordered successfully. Transaction details:"); + print(ordered_addon_transaction); +} catch (err) { + print("Error ordering server addon: " + err); +} +*/ + +// 14. Query a specific server addon transaction by ID +// This section demonstrates how to fetch details for a specific server addon transaction. +// Uncomment the following lines and set ADDON_TRANSACTION_ID to an actual transaction ID to use. +/* +print("\nAttempting to fetch specific server addon transaction with ID: " + ADDON_TRANSACTION_ID); +try { + let queried_addon_transaction = hetzner.get_server_addon_transaction_by_id(ADDON_TRANSACTION_ID); + print("Specific server addon transaction details:"); + print(queried_addon_transaction); +} catch (err) { + print("Error fetching specific server addon transaction: " + err); +} */ \ No newline at end of file diff --git a/src/api/mod.rs b/src/api/mod.rs index 2de719a..188c04b 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,14 +1,16 @@ pub mod error; pub mod models; -use self::models::{Boot, Rescue, Server, SshKey}; -use crate::api::error::ApiError; -use crate::api::models::{ +use self::models::{ + Boot, Rescue, Server, SshKey, ServerAddonProduct, ServerAddonProductWrapper, AuctionServerProduct, AuctionServerProductWrapper, AuctionTransaction, AuctionTransactionWrapper, BootWrapper, Cancellation, CancellationWrapper, OrderServerBuilder, OrderServerProduct, OrderServerProductWrapper, RescueWrapped, ServerWrapper, SshKeyWrapper, Transaction, TransactionWrapper, + ServerAddonTransaction, ServerAddonTransactionWrapper, + OrderServerAddonBuilder, }; +use crate::api::error::ApiError; use crate::config::Config; use error::AppError; use reqwest::blocking::Client as HttpClient; @@ -378,6 +380,25 @@ impl Client { let wrapped: AuctionTransactionWrapper = self.handle_response(response)?; Ok(wrapped.transaction) } + + pub fn get_server_addon_products( + &self, + server_number: i64, + ) -> Result, AppError> { + let response = self + .http_client + .get(format!( + "{}/order/server_addon/{}/product", + &self.config.api_url, server_number + )) + .basic_auth(&self.config.username, Some(&self.config.password)) + .send()?; + + let wrapped: Vec = self.handle_response(response)?; + let products = wrapped.into_iter().map(|sap| sap.product).collect(); + Ok(products) + } + pub fn order_auction_server( &self, product_id: i64, @@ -428,4 +449,65 @@ impl Client { let wrapped: AuctionTransactionWrapper = self.handle_response(response)?; Ok(wrapped.transaction) } + + pub fn get_server_addon_transactions(&self) -> Result, AppError> { + let response = self + .http_client + .get(format!("{}/order/server_addon/transaction", &self.config.api_url)) + .basic_auth(&self.config.username, Some(&self.config.password)) + .send()?; + + let wrapped: Vec = self.handle_response(response)?; + let transactions = wrapped.into_iter().map(|satw| satw.transaction).collect(); + Ok(transactions) + } + + pub fn get_server_addon_transaction_by_id( + &self, + transaction_id: &str, + ) -> Result { + let response = self + .http_client + .get(format!( + "{}/order/server_addon/transaction/{}", + &self.config.api_url, transaction_id + )) + .basic_auth(&self.config.username, Some(&self.config.password)) + .send()?; + + let wrapped: ServerAddonTransactionWrapper = self.handle_response(response)?; + Ok(wrapped.transaction) + } + + pub fn order_server_addon( + &self, + order: OrderServerAddonBuilder, + ) -> Result { + let mut params = json!({ + "server_number": order.server_number, + "product_id": order.product_id, + }); + + if let Some(reason) = order.reason { + params["reason"] = json!(reason); + } + if let Some(gateway) = order.gateway { + params["gateway"] = json!(gateway); + } + if let Some(test) = order.test { + if test { + params["test"] = json!(test); + } + } + + let response = self + .http_client + .post(format!("{}/order/server_addon/transaction", &self.config.api_url)) + .basic_auth(&self.config.username, Some(&self.config.password)) + .json(¶ms) + .send()?; + + let wrapped: ServerAddonTransactionWrapper = self.handle_response(response)?; + Ok(wrapped.transaction) + } } diff --git a/src/api/models.rs b/src/api/models.rs index e1ced43..e2b4aeb 100644 --- a/src/api/models.rs +++ b/src/api/models.rs @@ -726,6 +726,175 @@ impl fmt::Display for OrderableAddon { } } +#[derive(Debug, Deserialize, Clone)] +pub struct ServerAddonProductWrapper { + pub product: ServerAddonProduct, +} + +#[derive(Debug, Deserialize, Clone, CustomType)] +#[rhai_type(extra = Self::build_rhai_type)] +pub struct ServerAddonProduct { + pub id: String, + pub name: String, + #[serde(rename = "type")] + pub product_type: String, + pub price: ProductPrice, +} + +impl ServerAddonProduct { + fn build_rhai_type(builder: &mut TypeBuilder) { + builder + .with_name("ServerAddonProduct") + .with_get("id", |p: &mut ServerAddonProduct| p.id.clone()) + .with_get("name", |p: &mut ServerAddonProduct| p.name.clone()) + .with_get("product_type", |p: &mut ServerAddonProduct| { + p.product_type.clone() + }) + .with_get("price", |p: &mut ServerAddonProduct| p.price.clone()) + .on_print(|p: &mut ServerAddonProduct| p.to_string()) + .with_fn("pretty_print", |p: &mut ServerAddonProduct| p.to_string()); + } +} + +impl fmt::Display for ServerAddonProduct { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut table = Table::new(); + table.add_row(row!["Property", "Value"]); + table.add_row(row!["ID", self.id.clone()]); + table.add_row(row!["Name", self.name.clone()]); + table.add_row(row!["Type", self.product_type.clone()]); + table.add_row(row!["Price", self.price.to_string()]); + write!(f, "{}", table) + } +} + +#[derive(Debug, Deserialize, Clone)] +pub struct ServerAddonTransactionWrapper { + pub transaction: ServerAddonTransaction, +} + +#[derive(Debug, Deserialize, Clone, CustomType)] +#[rhai_type(extra = Self::build_rhai_type)] +pub struct ServerAddonTransaction { + pub id: String, + pub date: String, + pub status: String, + pub server_number: i32, + pub product: ServerAddonTransactionProduct, + pub resources: Vec, +} + +impl ServerAddonTransaction { + fn build_rhai_type(builder: &mut TypeBuilder) { + builder + .with_name("ServerAddonTransaction") + .with_get("id", |t: &mut ServerAddonTransaction| t.id.clone()) + .with_get("date", |t: &mut ServerAddonTransaction| t.date.clone()) + .with_get("status", |t: &mut ServerAddonTransaction| t.status.clone()) + .with_get("server_number", |t: &mut ServerAddonTransaction| { + t.server_number + }) + .with_get("product", |t: &mut ServerAddonTransaction| { + t.product.clone() + }) + .with_get("resources", |t: &mut ServerAddonTransaction| { + t.resources.clone() + }) + .on_print(|t: &mut ServerAddonTransaction| t.to_string()) + .with_fn("pretty_print", |t: &mut ServerAddonTransaction| { + t.to_string() + }); + } +} + +impl fmt::Display for ServerAddonTransaction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut table = Table::new(); + table.add_row(row!["Property", "Value"]); + table.add_row(row!["ID", self.id.clone()]); + table.add_row(row!["Date", self.date.clone()]); + table.add_row(row!["Status", self.status.clone()]); + table.add_row(row!["Server Number", self.server_number.to_string()]); + table.add_row(row!["Product ID", self.product.id.clone()]); + table.add_row(row!["Product Name", self.product.name.clone()]); + table.add_row(row!["Product Price", self.product.price.to_string()]); + + let mut resources_table = Table::new(); + resources_table.add_row(row![b => "Type", "ID"]); + for resource in &self.resources { + resources_table.add_row(row![resource.resource_type, resource.id]); + } + table.add_row(row!["Resources", resources_table]); + + write!(f, "{}", table) + } +} + +#[derive(Debug, Deserialize, Clone, CustomType)] +#[rhai_type(extra = Self::build_rhai_type)] +pub struct ServerAddonTransactionProduct { + pub id: String, + pub name: String, + pub price: ProductPrice, +} + +impl ServerAddonTransactionProduct { + fn build_rhai_type(builder: &mut TypeBuilder) { + builder + .with_name("ServerAddonTransactionProduct") + .with_get("id", |p: &mut ServerAddonTransactionProduct| p.id.clone()) + .with_get("name", |p: &mut ServerAddonTransactionProduct| { + p.name.clone() + }) + .with_get("price", |p: &mut ServerAddonTransactionProduct| { + p.price.clone() + }) + .on_print(|p: &mut ServerAddonTransactionProduct| p.to_string()) + .with_fn("pretty_print", |p: &mut ServerAddonTransactionProduct| { + p.to_string() + }); + } +} + +impl fmt::Display for ServerAddonTransactionProduct { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "ID: {}, Name: {}, Price: ({})", + self.id, self.name, self.price + ) + } +} + +#[derive(Debug, Deserialize, Clone, CustomType)] +#[rhai_type(extra = Self::build_rhai_type)] +pub struct ServerAddonResource { + #[serde(rename = "type")] + pub resource_type: String, + pub id: String, +} + +impl ServerAddonResource { + fn build_rhai_type(builder: &mut TypeBuilder) { + builder + .with_name("ServerAddonResource") + .with_get("resource_type", |r: &mut ServerAddonResource| { + r.resource_type.clone() + }) + .with_get("id", |r: &mut ServerAddonResource| r.id.clone()) + .on_print(|r: &mut ServerAddonResource| r.to_string()) + .with_fn("pretty_print", |r: &mut ServerAddonResource| { + r.to_string() + }); + } +} + +impl fmt::Display for ServerAddonResource { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Type: {}, ID: {}", self.resource_type, self.id) + } +} + #[derive(Debug, Deserialize, Clone)] pub struct OrderServerProductWrapper { pub product: OrderServerProduct, @@ -1544,6 +1713,63 @@ impl OrderAuctionServerBuilder { } } +#[derive(Debug, Deserialize, Clone, CustomType)] +#[rhai_type(extra = Self::build_rhai_type)] +pub struct OrderServerAddonBuilder { + pub server_number: i64, + pub product_id: String, + pub reason: Option, + pub gateway: Option, + pub test: Option, +} + +impl OrderServerAddonBuilder { + pub fn new(server_number: i64, product_id: &str) -> Self { + Self { + server_number, + product_id: product_id.to_string(), + reason: None, + gateway: None, + test: Some(true), // by default test is enabled + } + } + + pub fn with_reason(mut self, reason: &str) -> Self { + self.reason = Some(reason.to_string()); + self + } + + pub fn with_gateway(mut self, gateway: &str) -> Self { + self.gateway = Some(gateway.to_string()); + self + } + + pub fn with_test(mut self, test: bool) -> Self { + self.test = Some(test); + self + } + + fn build_rhai_type(builder: &mut TypeBuilder) { + builder + .with_name("OrderServerAddonBuilder") + .with_fn("new_server_addon_builder", Self::new) + .with_fn("with_reason", Self::with_reason) + .with_fn("with_gateway", Self::with_gateway) + .with_fn("with_test", Self::with_test) + .with_get("server_number", |b: &mut OrderServerAddonBuilder| { + b.server_number + }) + .with_get("product_id", |b: &mut OrderServerAddonBuilder| { + b.product_id.clone() + }) + .with_get("reason", |b: &mut OrderServerAddonBuilder| b.reason.clone()) + .with_get("gateway", |b: &mut OrderServerAddonBuilder| { + b.gateway.clone() + }) + .with_get("test", |b: &mut OrderServerAddonBuilder| b.test.clone()); + } +} + fn string_or_seq_string<'de, D>(deserializer: D) -> Result, D::Error> where diff --git a/src/scripting/mod.rs b/src/scripting/mod.rs index 3e36734..83e6c8b 100644 --- a/src/scripting/mod.rs +++ b/src/scripting/mod.rs @@ -1,6 +1,6 @@ use crate::api::Client; use crate::api::models::{ - AuctionServerProduct, AuctionTransaction, AuctionTransactionProduct, AuthorizedKey, Boot, Cancellation, Cpanel, HostKey, Linux, OrderAuctionServerBuilder, OrderServerBuilder, OrderServerProduct, Plesk, Rescue, Server, SshKey, Transaction, TransactionProduct, Vnc, Windows + AuctionServerProduct, AuctionTransaction, AuctionTransactionProduct, AuthorizedKey, Boot, Cancellation, Cpanel, HostKey, Linux, OrderAuctionServerBuilder, OrderServerBuilder, OrderServerProduct, Plesk, Rescue, Server, ServerAddonProduct, ServerAddonProductWrapper, ServerAddonTransaction, ServerAddonResource, SshKey, Transaction, TransactionProduct, Vnc, Windows, OrderServerAddonBuilder }; use rhai::{Engine, Scope}; @@ -34,6 +34,10 @@ pub fn setup_engine(client: Client) -> (Engine, Scope<'static>) { engine.build_type::(); engine.build_type::(); engine.build_type::(); + engine.build_type::(); + engine.build_type::(); + engine.build_type::(); + engine.build_type::(); server::register(&mut engine); ssh_keys::register(&mut engine); diff --git a/src/scripting/printing/mod.rs b/src/scripting/printing/mod.rs index ea7a948..45c7dc4 100644 --- a/src/scripting/printing/mod.rs +++ b/src/scripting/printing/mod.rs @@ -1,5 +1,5 @@ use rhai::{Array, Engine}; -use crate::{api::models::{OrderServerProduct, AuctionServerProduct, AuctionTransaction}, scripting::{Server, SshKey}}; +use crate::{api::models::{OrderServerProduct, AuctionServerProduct, AuctionTransaction, ServerAddonProduct, ServerAddonTransaction}, scripting::{Server, SshKey}}; mod servers_table; mod ssh_keys_table; @@ -25,6 +25,10 @@ pub fn pretty_print_dispatch(array: Array) { server_ordering_table::pretty_print_auction_server_products(array); } else if first.is::() { server_ordering_table::pretty_print_auction_transactions(array); + } else if first.is::() { + server_ordering_table::pretty_print_server_addon_products(array); + } else if first.is::() { + server_ordering_table::pretty_print_server_addon_transactions(array); } else { // Generic fallback for other types for item in array { diff --git a/src/scripting/printing/server_ordering_table.rs b/src/scripting/printing/server_ordering_table.rs index 2346923..95d4cf7 100644 --- a/src/scripting/printing/server_ordering_table.rs +++ b/src/scripting/printing/server_ordering_table.rs @@ -1,5 +1,5 @@ use prettytable::{row, Table}; -use crate::api::models::OrderServerProduct; +use crate::api::models::{OrderServerProduct, ServerAddonProduct, ServerAddonTransaction, ServerAddonResource}; pub fn pretty_print_server_products(products: rhai::Array) { let mut table = Table::new(); @@ -126,6 +126,40 @@ pub fn pretty_print_auction_server_products(products: rhai::Array) { table.printstd(); } +pub fn pretty_print_server_addon_products(products: rhai::Array) { + let mut table = Table::new(); + table.add_row(row![b => + "ID", + "Name", + "Type", + "Location", + "Price (Net)", + "Price (Gross)", + "Hourly Net", + "Hourly Gross", + "Setup Net", + "Setup Gross", + ]); + + for product_dyn in products { + if let Some(product) = product_dyn.try_cast::() { + table.add_row(row![ + product.id, + product.name, + product.product_type, + product.price.location, + product.price.price.net, + product.price.price.gross, + product.price.price.hourly_net, + product.price.price.hourly_gross, + product.price.price_setup.net, + product.price.price_setup.gross, + ]); + } + } + table.printstd(); +} + pub fn pretty_print_auction_transactions(transactions: rhai::Array) { let mut table = Table::new(); table.add_row(row![b => @@ -220,4 +254,40 @@ pub fn pretty_print_auction_transactions(transactions: rhai::Array) { } } table.printstd(); +} + +pub fn pretty_print_server_addon_transactions(transactions: rhai::Array) { + let mut table = Table::new(); + table.add_row(row![b => + "ID", + "Date", + "Status", + "Server Number", + "Product ID", + "Product Name", + "Product Price", + "Resources", + ]); + + for transaction_dyn in transactions { + if let Some(transaction) = transaction_dyn.try_cast::() { + let mut resources_table = Table::new(); + resources_table.add_row(row![b => "Type", "ID"]); + for resource in &transaction.resources { + resources_table.add_row(row![resource.resource_type, resource.id]); + } + + table.add_row(row![ + transaction.id, + transaction.date, + transaction.status, + transaction.server_number, + transaction.product.id, + transaction.product.name, + transaction.product.price.to_string(), + resources_table, + ]); + } + } + table.printstd(); } \ No newline at end of file diff --git a/src/scripting/server_ordering.rs b/src/scripting/server_ordering.rs index 6df77e4..a979161 100644 --- a/src/scripting/server_ordering.rs +++ b/src/scripting/server_ordering.rs @@ -2,7 +2,7 @@ use crate::api::{ Client, models::{ AuctionServerProduct, AuctionTransaction, OrderAuctionServerBuilder, OrderServerBuilder, - OrderServerProduct, Transaction, + OrderServerProduct, ServerAddonProduct, ServerAddonTransaction, Transaction, }, }; use rhai::{Array, Dynamic, plugin::*}; @@ -14,6 +14,8 @@ pub fn register(engine: &mut Engine) { #[export_module] pub mod server_order_api { + use crate::api::models::OrderServerAddonBuilder; + #[rhai_fn(name = "get_server_products", return_raw)] pub fn get_server_ordering_product_overview( client: &mut Client, @@ -103,6 +105,38 @@ pub mod server_order_api { Ok(transaction) } + #[rhai_fn(name = "get_server_addon_products", return_raw)] + pub fn get_server_addon_products( + client: &mut Client, + server_number: i64, + ) -> Result> { + let products = client + .get_server_addon_products(server_number) + .map_err(|e| Into::>::into(e.to_string()))?; + Ok(products.into_iter().map(Dynamic::from).collect()) + } + + #[rhai_fn(name = "get_server_addon_transactions", return_raw)] + pub fn get_server_addon_transactions( + client: &mut Client, + ) -> Result> { + let transactions = client + .get_server_addon_transactions() + .map_err(|e| Into::>::into(e.to_string()))?; + Ok(transactions.into_iter().map(Dynamic::from).collect()) + } + + #[rhai_fn(name = "get_server_addon_transaction_by_id", return_raw)] + pub fn get_server_addon_transaction_by_id( + client: &mut Client, + transaction_id: &str, + ) -> Result> { + let transaction = client + .get_server_addon_transaction_by_id(transaction_id) + .map_err(|e| Into::>::into(e.to_string()))?; + Ok(transaction) + } + #[rhai_fn(name = "order_auction_server", return_raw)] pub fn order_auction_server( client: &mut Client, @@ -121,4 +155,16 @@ pub mod server_order_api { ).map_err(|e| Into::>::into(e.to_string()))?; Ok(transaction) } + + #[rhai_fn(name = "order_server_addon", return_raw)] + pub fn order_server_addon( + client: &mut Client, + order: OrderServerAddonBuilder, + ) -> Result> { + println!("Builder struct being used to order server addon: {:#?}", order); + let transaction = client + .order_server_addon(order) + .map_err(|e| Into::>::into(e.to_string()))?; + Ok(transaction) + } }