├── .editorconfig ├── .env.example ├── .gitignore ├── Cargo.toml ├── LICENSE.md ├── README.md ├── examples ├── README.md ├── create_deployment.rs └── create_secret.rs └── src ├── client.rs ├── hop.rs ├── lib.rs ├── main.rs ├── sdks ├── channels.rs ├── ignite.rs ├── mod.rs ├── pipe.rs ├── projects.rs ├── registry.rs └── users.rs ├── types ├── channels.rs ├── ignite.rs ├── mod.rs ├── pipe.rs ├── projects.rs ├── registry.rs └── users.rs └── utils.rs /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Tokens for running examples 2 | PROJECT_TOKEN="ptk_xxx" 3 | PERSONAL_TOKEN="pat_xxx" 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | 4 | .idea 5 | .vscode 6 | 7 | .env 8 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hop" 3 | description = "hop sdk rust" 4 | authors = ["tomheaton"] 5 | version = "0.0.0" 6 | edition = "2021" 7 | include = [ 8 | "src/**/*", 9 | "Cargo.toml", 10 | "README.md", 11 | "LICENSE.md", 12 | ] 13 | documentation = "https://docs.rs/hop" 14 | # TODO: move to hopinc 15 | homepage = "https://github.com/tomheaton/hop-rs" 16 | repository = "https://github.com/tomheaton/hop-rs" 17 | license = "MIT" 18 | 19 | [dependencies] 20 | serde_json = "1.0.70" 21 | reqwest = { version = "0.11.6", features = ["json", "blocking"] } 22 | tokio = { version = "1.24.1", features = ["full"] } 23 | serde = { version = "1.0.152", features = ["derive"] } 24 | 25 | [dev-dependencies] 26 | tokio = { version = "1.24.1", features = ["full"] } 27 | rand = "0.8.5" 28 | dotenv = "0.15.0" 29 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Tom Heaton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hop-rs (wip) 2 | 3 | Hop's Rust library. Requires Rust 1.61+ 4 | 5 | ## Installation 6 | 7 | ```toml 8 | [dependencies] 9 | hop = "0.0.0" 10 | ``` 11 | 12 | ## Usage 13 | 14 | Create a [project token](https://docs.hop.io/reference/project-tokens) or personal access token. 15 | 16 | ```rust 17 | extern crate hop; 18 | extern crate rand; 19 | 20 | use hop::Hop; 21 | use rand::Rng; 22 | 23 | #[tokio::main] 24 | async fn main() { 25 | let my_token = "ptk_xxx"; 26 | let hop = Hop::new(my_token); 27 | 28 | // Example: Creating a project secret 29 | hop.projects.create_secret( 30 | "RANDOM_NUMBER", 31 | rand::thread_rng().gen_range(0..100).to_string(), 32 | ).await.unwrap(); 33 | } 34 | ``` 35 | 36 | [//]: # (// let projects = hop.projects().list().unwrap();) 37 | [//]: # (// println!("{:?}", projects);) 38 | 39 | ## Examples 40 | 41 | To run examples, add a Personal token and a Project token to the `.env` file. 42 | 43 | ```bash 44 | cargo run --example 45 | ``` 46 | 47 | Examples can be found [here](./examples). 48 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | - [Create a deployment](./create_deployment.rs) 4 | - [Create a secret](./create_secret.rs) 5 | -------------------------------------------------------------------------------- /examples/create_deployment.rs: -------------------------------------------------------------------------------- 1 | extern crate dotenv; 2 | extern crate hop; 3 | extern crate rand; 4 | 5 | use std::collections::HashMap; 6 | use std::env; 7 | 8 | use dotenv::dotenv; 9 | use rand::Rng; 10 | 11 | use hop::Hop; 12 | use hop::types::ignite::{CreateDeploymentConfig, Image, Resources, RestartPolicy, RuntimeType, UpdateDeploymentConfig, VolumeDefinition, VolumeFormat}; 13 | 14 | #[tokio::main] 15 | async fn main() { 16 | dotenv().ok(); 17 | 18 | // let my_token = "ptk_xxx"; 19 | let my_token = env::var("PROJECT_TOKEN").expect("PROJECT_TOKEN needed!"); 20 | let hop = Hop::new(my_token.as_str()); 21 | 22 | // Example: Creating a deployment 23 | let deployment = hop.ignite.create_deployment( 24 | CreateDeploymentConfig::new( 25 | "postgres", 26 | RuntimeType::Stateful, 27 | "2022-12-28", 28 | None, 29 | Image::new( 30 | Some("postgres"), 31 | None, 32 | None, 33 | ), 34 | Some(HashMap::from([ 35 | ("POSTGRES_PASSWORD", "password") 36 | ])), 37 | Resources::new( 38 | 1f64, 39 | "1GB", 40 | None, 41 | ), 42 | RestartPolicy::Never, 43 | Some(VolumeDefinition { 44 | fs: VolumeFormat::EXT4, 45 | size: "1GB".to_string(), 46 | mountpath: "/lol".to_string(), 47 | }), 48 | None, 49 | ), 50 | ).await.unwrap(); 51 | 52 | println!("deployment: {:#?}", deployment); 53 | 54 | // let updated_deployment = hop.ignite.update_deployment( 55 | // deployment.id.as_str(), 56 | // // TODO: fix this 57 | // // if None values are removed from the sent config, they won't actually update the config 58 | // // if None values are kept in the sent config, they will be updated to None 59 | // UpdateDeploymentConfig { 60 | // name: None, 61 | // container_strategy: None, 62 | // runtime_type: None, 63 | // version: None, 64 | // cmd: None, 65 | // image: None, 66 | // env: None, 67 | // resources: None, 68 | // restart_policy: None, 69 | // volume: None, 70 | // entrypoint: None, 71 | // } 72 | // ).await.unwrap(); 73 | // 74 | // println!("updated_deployment: {:#?}", updated_deployment); 75 | } 76 | -------------------------------------------------------------------------------- /examples/create_secret.rs: -------------------------------------------------------------------------------- 1 | extern crate dotenv; 2 | extern crate hop; 3 | extern crate rand; 4 | 5 | use std::env; 6 | 7 | use dotenv::dotenv; 8 | use rand::Rng; 9 | 10 | use hop::Hop; 11 | 12 | #[tokio::main] 13 | async fn main() { 14 | dotenv().ok(); 15 | 16 | // let my_token = "ptk_xxx"; 17 | let my_token = env::var("PROJECT_TOKEN").expect("PROJECT_TOKEN needed!"); 18 | let hop = Hop::new(my_token.as_str()); 19 | 20 | // Example: Creating a project secret 21 | let secret = hop.projects.create_secret( 22 | "RANDOM_NUMBER", 23 | rand::thread_rng().gen_range(0..100).to_string(), 24 | ).await.unwrap(); 25 | 26 | println!("secret: {:#?}", secret); 27 | } 28 | -------------------------------------------------------------------------------- /src/client.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | 3 | use crate::types::APIError; 4 | 5 | const BASE_URL: &str = "https://api.hop.io"; 6 | 7 | #[derive(Clone)] 8 | pub struct APIClient { 9 | pub token: String, 10 | } 11 | 12 | impl APIClient { 13 | pub fn new( 14 | token: &str, 15 | ) -> APIClient { 16 | return APIClient { 17 | token: token.to_string(), 18 | }; 19 | } 20 | 21 | pub async fn get( 22 | &self, 23 | url: &str, 24 | ) -> Result { 25 | let client = reqwest::Client::new(); 26 | 27 | let response = client 28 | .get(format!("{}{}", BASE_URL, url).as_str()) 29 | .header("Authorization", self.token.as_str()) 30 | .header("Content-Type", "application/json") 31 | .send() 32 | .await 33 | .unwrap(); 34 | 35 | if response.status() != 200 { 36 | // println!("status: {}", response.status()); 37 | println!("response: {}", response.text().await.unwrap()); 38 | return Err(APIError); 39 | } 40 | 41 | let data: serde_json::Value = response.json().await.unwrap(); 42 | println!("response: {}", serde_json::to_string_pretty(&data).unwrap()); 43 | 44 | return Ok(data); 45 | } 46 | 47 | pub async fn post( 48 | &self, 49 | url: &str, 50 | data: T, 51 | ) -> Result { 52 | let client = reqwest::Client::new(); 53 | 54 | // println!("{}", format!("{}{}", BASE_URL, url).as_str()); 55 | // println!("{}", serde_json::to_string_pretty(&data).unwrap()); 56 | 57 | let response = client 58 | .post(format!("{}{}", BASE_URL, url).as_str()) 59 | .header("Authorization", self.token.as_str()) 60 | .header("Content-Type", "application/json") 61 | .json(&data) 62 | .send() 63 | .await 64 | .unwrap(); 65 | 66 | // TODO: handle returning empty response 67 | if response.status() == 201 { 68 | println!("POST Successful!"); 69 | return Ok(serde_json::json!({})); 70 | } 71 | 72 | if response.status() != 200 { 73 | // println!("status: {}", response.status()); 74 | println!("response: {}", response.text().await.unwrap()); 75 | return Err(APIError); 76 | } 77 | 78 | let data: serde_json::Value = response.json().await.unwrap(); 79 | println!("response: {}", serde_json::to_string_pretty(&data).unwrap()); 80 | 81 | return Ok(data); 82 | } 83 | 84 | pub async fn put( 85 | &self, 86 | url: &str, 87 | data: T, 88 | ) -> Result { 89 | let client = reqwest::Client::new(); 90 | 91 | // println!("{}", format!("{}{}", BASE_URL, url).as_str()); 92 | // println!("{}", serde_json::to_string_pretty(&data).unwrap()); 93 | 94 | let response = client 95 | .put(format!("{}{}", BASE_URL, url).as_str()) 96 | .header("Authorization", self.token.as_str()) 97 | .header("Content-Type", "application/json") 98 | .json(&data) 99 | .send() 100 | .await 101 | .unwrap(); 102 | 103 | if response.status() != 200 { 104 | // println!("status: {}", response.status()); 105 | println!("response: {}", response.text().await.unwrap()); 106 | return Err(APIError); 107 | } 108 | 109 | let data: serde_json::Value = response.json().await.unwrap(); 110 | println!("response: {}", serde_json::to_string_pretty(&data).unwrap()); 111 | 112 | return Ok(data); 113 | } 114 | 115 | // TODO: merge this with put 116 | pub async fn put_none( 117 | &self, 118 | url: &str, 119 | ) -> Result<(), APIError> { 120 | let client = reqwest::Client::new(); 121 | 122 | println!("{}", format!("{}{}", BASE_URL, url).as_str()); 123 | // println!("{}", serde_json::to_string_pretty(&data).unwrap()); 124 | 125 | let response = client 126 | .put(format!("{}{}", BASE_URL, url).as_str()) 127 | .header("Authorization", self.token.as_str()) 128 | .send() 129 | .await 130 | .unwrap(); 131 | 132 | if response.status() != 201 { 133 | // println!("status: {}", response.status()); 134 | println!("response: {}", response.text().await.unwrap()); 135 | return Err(APIError); 136 | } 137 | 138 | println!("PUT Successful!"); 139 | return Ok(()); 140 | } 141 | 142 | // TODO: this is a hack, we should be able to use the same function for both 143 | pub async fn put_raw( 144 | &self, 145 | url: &str, 146 | data: String, 147 | ) -> Result { 148 | let client = reqwest::Client::new(); 149 | 150 | let response = client 151 | .put(format!("{}{}", BASE_URL, url).as_str()) 152 | .header("Authorization", self.token.as_str()) 153 | .header("Content-Type", "text/plain") 154 | .body(data) 155 | .send() 156 | .await 157 | .unwrap(); 158 | 159 | if response.status() != 200 { 160 | // println!("status: {}", response.status()); 161 | println!("response: {}", response.text().await.unwrap()); 162 | return Err(APIError); 163 | } 164 | 165 | let data: serde_json::Value = response.json().await.unwrap(); 166 | println!("response: {}", serde_json::to_string_pretty(&data).unwrap()); 167 | 168 | return Ok(data); 169 | } 170 | 171 | pub async fn patch( 172 | &self, 173 | url: &str, 174 | data: T, 175 | ) -> Result { 176 | let client = reqwest::Client::new(); 177 | 178 | // println!("{}", format!("{}{}", BASE_URL, url).as_str()); 179 | // println!("{}", serde_json::to_string_pretty(&data).unwrap()); 180 | 181 | let response = client 182 | .patch(format!("{}{}", BASE_URL, url).as_str()) 183 | .header("Authorization", self.token.as_str()) 184 | .header("Content-Type", "application/json") 185 | .json(&data) 186 | .send() 187 | .await 188 | .unwrap(); 189 | 190 | if response.status() != 200 { 191 | // println!("status: {}", response.status()); 192 | println!("response: {}", response.text().await.unwrap()); 193 | return Err(APIError); 194 | } 195 | 196 | let data: serde_json::Value = response.json().await.unwrap(); 197 | println!("response: {}", serde_json::to_string_pretty(&data).unwrap()); 198 | 199 | return Ok(data); 200 | } 201 | 202 | pub async fn delete( 203 | &self, 204 | url: &str, 205 | ) -> Result<(), APIError> { 206 | let client = reqwest::Client::new(); 207 | 208 | let response = client 209 | .delete(format!("{}{}", BASE_URL, url).as_str()) 210 | .header("Authorization", self.token.as_str()) 211 | .send() 212 | .await 213 | .unwrap(); 214 | 215 | // TODO: check other functions for correct status code 216 | if response.status() != 204 { 217 | // println!("status: {}", response.status()); 218 | println!("response: {}", response.text().await.unwrap()); 219 | 220 | // TODO: parse json response here, because no data returned from 204 DELETE 221 | // let data: serde_json::Value = response.json().await.unwrap(); 222 | // println!("response: {}", serde_json::to_string_pretty(&data).unwrap()); 223 | 224 | return Err(APIError); 225 | } 226 | 227 | println!("DELETE Successful!"); 228 | 229 | return Ok(()); 230 | } 231 | 232 | // TODO: merge this with delete 233 | pub async fn delete_with_return( 234 | &self, 235 | url: &str, 236 | ) -> Result { 237 | let client = reqwest::Client::new(); 238 | 239 | let response = client 240 | .delete(format!("{}{}", BASE_URL, url).as_str()) 241 | .header("Authorization", self.token.as_str()) 242 | .send() 243 | .await 244 | .unwrap(); 245 | 246 | // TODO: check other functions for correct status code 247 | if response.status() != 200 { 248 | // println!("status: {}", response.status()); 249 | println!("response: {}", response.text().await.unwrap()); 250 | 251 | // TODO: parse json response here, because no data returned from 204 DELETE 252 | // let data: serde_json::Value = response.json().await.unwrap(); 253 | // println!("response: {}", serde_json::to_string_pretty(&data).unwrap()); 254 | 255 | return Err(APIError); 256 | } 257 | 258 | println!("DELETE Successful!"); 259 | 260 | let data: serde_json::Value = response.json().await.unwrap(); 261 | println!("response: {}", serde_json::to_string_pretty(&data).unwrap()); 262 | 263 | return Ok(data); 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /src/hop.rs: -------------------------------------------------------------------------------- 1 | use crate::client::APIClient; 2 | use crate::sdks::channels::Channels; 3 | use crate::sdks::ignite::Ignite; 4 | use crate::sdks::pipe::Pipe; 5 | use crate::sdks::projects::Projects; 6 | use crate::sdks::registry::Registry; 7 | use crate::sdks::users::Users; 8 | 9 | pub struct Hop { 10 | // pub client: APIClient, 11 | 12 | pub channels: Channels, 13 | pub ignite: Ignite, 14 | pub pipe: Pipe, 15 | pub projects: Projects, 16 | pub registry: Registry, 17 | pub users: Users, 18 | } 19 | 20 | impl Hop { 21 | pub fn new( 22 | token: &str, 23 | ) -> Hop { 24 | println!("Creating a new Hop client with token {}", token); 25 | 26 | let is_ptk = token.starts_with("ptk_"); 27 | let is_pat = token.starts_with("pat_"); 28 | 29 | if !is_ptk && !is_pat { 30 | panic!("Invalid token type. Must be a project token or a personal access token."); 31 | } 32 | 33 | // TODO: create client here 34 | // let client = APIClient::new( 35 | // token, 36 | // ); 37 | 38 | return Hop { 39 | // client, 40 | 41 | channels: Channels::new(token), 42 | ignite: Ignite::new(token), 43 | pipe: Pipe::new(token), 44 | projects: Projects::new(token), 45 | registry: Registry::new(token), 46 | users: Users::new(token), 47 | }; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // TODO: are wildcard imports a good idea? 2 | pub use client::*; 3 | pub use hop::Hop; 4 | pub use sdks::*; 5 | pub use types::*; 6 | pub use utils::*; 7 | 8 | pub mod client; 9 | pub mod hop; 10 | pub mod sdks; 11 | pub mod types; 12 | pub mod utils; 13 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate hop; 2 | 3 | #[tokio::main] 4 | async fn main() { 5 | println!("hop"); 6 | } 7 | -------------------------------------------------------------------------------- /src/sdks/channels.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::client::APIClient; 4 | use crate::types::APIError; 5 | use crate::types::channels::{Channel, ChannelToken, ChannelType}; 6 | 7 | pub struct Channels { 8 | pub token: String, 9 | pub client: APIClient, 10 | } 11 | 12 | impl Channels { 13 | pub fn new( 14 | token: &str, 15 | ) -> Channels { 16 | return Channels { 17 | token: token.to_owned(), 18 | client: APIClient::new(token), 19 | }; 20 | } 21 | 22 | // Channels: 23 | 24 | pub async fn get_channels( 25 | &self 26 | ) -> Result, APIError> { 27 | println!("Getting all channels"); 28 | 29 | let response = self.client.get( 30 | "/v1/channels" 31 | ).await.unwrap(); 32 | 33 | let channels = response["data"]["channels"].to_owned(); 34 | 35 | return Ok(serde_json::from_value(channels).unwrap()); 36 | } 37 | 38 | pub async fn get_channel( 39 | &self, 40 | channel_id: &str, 41 | ) -> Result { 42 | println!("Getting a channel"); 43 | 44 | let response = self.client.get( 45 | format!("/v1/channels/{}", channel_id).as_str() 46 | ).await.unwrap(); 47 | 48 | let channel = response["data"]["channel"].to_owned(); 49 | 50 | return Ok(serde_json::from_value(channel).unwrap()); 51 | } 52 | 53 | pub async fn create_channel( 54 | &self, 55 | channel_type: ChannelType, 56 | channel_id: Option<&str>, 57 | // TODO: use raw serde_json here to allow any value? 58 | state: Option>, 59 | ) -> Result { 60 | println!("Creating a channel"); 61 | 62 | let response; 63 | 64 | match channel_id { 65 | Some(id) => { 66 | response = self.client.put( 67 | format!("/v1/channels/{}", id).as_str(), 68 | serde_json::json!({ 69 | "type": channel_type, 70 | "state": state, 71 | }), 72 | ).await.unwrap(); 73 | } 74 | None => { 75 | response = self.client.post( 76 | "/v1/channels", 77 | serde_json::json!({ 78 | "type": channel_type, 79 | "state": state, 80 | }), 81 | ).await.unwrap(); 82 | } 83 | } 84 | 85 | let channel = response["data"]["channel"].to_owned(); 86 | 87 | return Ok(serde_json::from_value(channel).unwrap()); 88 | } 89 | 90 | pub async fn delete_channel( 91 | &self, 92 | channel_id: &str, 93 | ) -> Result<(), APIError> { 94 | println!("Deleting a channel"); 95 | 96 | return self.client.delete( 97 | format!("/v1/channels/{}", channel_id).as_str() 98 | ).await; 99 | } 100 | 101 | pub async fn get_stats( 102 | &self, 103 | channel_id: &str, 104 | // TODO: improve this (ask hop about this) 105 | ) -> Result, APIError> { 106 | let response = self.client.get( 107 | format!("/v1/channels/{}/stats", channel_id).as_str() 108 | ).await.unwrap(); 109 | 110 | let stats = response["data"]["stats"].to_owned(); 111 | 112 | return Ok(serde_json::from_value(stats).unwrap()); 113 | } 114 | 115 | pub async fn subscribe_tokens( 116 | &self, 117 | channel_id: &str, 118 | tokens: Vec<&str>, 119 | ) -> Result<(), APIError> { 120 | for token in tokens { 121 | self.client.put_none( 122 | format!("/v1/channels/{}/subscribers/{}", channel_id, token).as_str(), 123 | ).await.unwrap(); 124 | } 125 | 126 | return Ok(()); 127 | } 128 | 129 | pub async fn subscribe_token( 130 | &self, 131 | channel_id: &str, 132 | token_id: &str, 133 | ) -> Result<(), APIError> { 134 | self.client.put_none( 135 | format!("/v1/channels/{}/subscribers/{}", channel_id, token_id).as_str(), 136 | ).await.unwrap(); 137 | 138 | return Ok(()); 139 | } 140 | 141 | /*pub async fn get_tokens( 142 | &self 143 | ) -> Result, APIError> { 144 | let response =self.client.get( 145 | format!("/v1/channels/{}/tokens", channel_id).as_str() 146 | ).await.unwrap(); 147 | 148 | let tokens = response["data"]["tokens"].to_owned(); 149 | 150 | return Ok(serde_json::from_value(tokens).unwrap()); 151 | }*/ 152 | 153 | pub async fn set_state( 154 | &self, 155 | channel_id: &str, 156 | state: HashMap, 157 | ) -> Result<(), APIError> { 158 | self.client.put( 159 | format!("/v1/channels/{}/state", channel_id).as_str(), 160 | serde_json::json!(state), 161 | ).await.unwrap(); 162 | 163 | return Ok(()); 164 | } 165 | 166 | pub async fn patch_state( 167 | &self, 168 | channel_id: &str, 169 | state: HashMap, 170 | ) -> Result<(), APIError> { 171 | self.client.patch( 172 | format!("/v1/channels/{}/state", channel_id).as_str(), 173 | serde_json::json!(state), 174 | ).await.unwrap(); 175 | 176 | return Ok(()); 177 | } 178 | 179 | pub async fn publish_message( 180 | &self, 181 | channel_id: &str, 182 | event: &str, 183 | data: HashMap, 184 | ) -> Result<(), APIError> { 185 | self.client.post( 186 | format!("/v1/channels/{}/messages", channel_id).as_str(), 187 | serde_json::json!({ 188 | "e": event, 189 | "d": data, 190 | }), 191 | ).await.unwrap(); 192 | 193 | return Ok(()); 194 | } 195 | 196 | // Tokens: 197 | 198 | pub async fn get_token( 199 | &self, 200 | token_id: &str, 201 | ) -> Result { 202 | let response = self.client.get( 203 | format!("/v1/channels/tokens/{}", token_id).as_str() 204 | ).await.unwrap(); 205 | 206 | let token = response["data"]["token"].to_owned(); 207 | 208 | return Ok(serde_json::from_value(token).unwrap()); 209 | } 210 | 211 | // TODO: check this function 212 | pub async fn create_token( 213 | &self, 214 | // TODO: use raw serde_json here to allow any value? 215 | state: Option>, 216 | // state: impl Into>>, 217 | ) -> Result { 218 | // TODO use into here? 219 | // let state = state.into(); 220 | 221 | // TODO: inline this? (intellisense not available inside the macro) 222 | let state = state.unwrap_or(HashMap::new()); 223 | 224 | let response = self.client.post( 225 | "/v1/channels/tokens", 226 | serde_json::json!(state), 227 | ).await.unwrap(); 228 | 229 | let mut token = response["data"]["token"].clone(); 230 | token["is_online"] = serde_json::json!(true); 231 | 232 | return Ok(serde_json::from_value(token).unwrap()); 233 | } 234 | 235 | pub async fn delete_token( 236 | &self, 237 | token_id: &str, 238 | ) -> Result<(), APIError> { 239 | return self.client.delete( 240 | format!("/v1/channels/tokens/{}", token_id).as_str() 241 | ).await; 242 | } 243 | 244 | pub async fn set_token_state( 245 | &self, 246 | token_id: &str, 247 | state: HashMap, 248 | ) -> Result { 249 | let response = self.client.patch( 250 | format!("/v1/channels/tokens/{}", token_id).as_str(), 251 | serde_json::json!(state), 252 | ).await.unwrap(); 253 | 254 | let mut token = response["data"]["token"].clone(); 255 | token["is_online"] = serde_json::json!(true); 256 | 257 | return Ok(serde_json::from_value(token).unwrap()); 258 | } 259 | 260 | pub async fn is_token_online( 261 | &self, 262 | token_id: &str, 263 | ) -> Result { 264 | println!("Checking if channel token is online"); 265 | 266 | let response = self.client.get( 267 | format!("/v1/channels/tokens/{}", token_id).as_str() 268 | ).await.unwrap(); 269 | 270 | let token = response["data"]["token"].to_owned(); 271 | let token = serde_json::from_value::(token).unwrap(); 272 | 273 | return Ok(token.is_online); 274 | } 275 | 276 | pub async fn publish_direct_message( 277 | &self, 278 | token_id: &str, 279 | event: &str, 280 | data: HashMap, 281 | ) -> Result<(), APIError> { 282 | self.client.post( 283 | format!("/v1/channels/tokens/{}/messages", token_id).as_str(), 284 | serde_json::json!({ 285 | "e": event, 286 | "d": data, 287 | }), 288 | ).await.unwrap(); 289 | 290 | return Ok(()); 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /src/sdks/ignite.rs: -------------------------------------------------------------------------------- 1 | use crate::{APIClient, APIError, get_bytes}; 2 | use crate::types::ignite::{Container, ContainerState, CreateDeploymentConfig, CreateHealthCheckConfig, Deployment, DeploymentConfig, DeploymentLog, Gateway, GatewayConfig, GatewayType, HealthCheck, Rollout, RuntimeType, StorageStats, UpdateDeploymentConfig, UpdateHealthCheckConfig}; 3 | 4 | const SIX_MB_IN_BYTES: i64 = 6 * 1024 * 1024; 5 | // TODO: remove this and set version to one value (ask hop) 6 | const VERSIONS: [&str; 4] = ["2022-05-17", "2022-10-19", "2022-12-12", "2022-12-28"]; 7 | 8 | pub struct Ignite { 9 | pub token: String, 10 | pub client: APIClient, 11 | } 12 | 13 | impl Ignite { 14 | pub fn new( 15 | token: &str, 16 | ) -> Ignite { 17 | return Ignite { 18 | token: token.to_owned(), 19 | client: APIClient::new(token), 20 | }; 21 | } 22 | 23 | // Deployments: 24 | 25 | pub async fn get_deployments( 26 | &self, 27 | ) -> Result, APIError> { 28 | let response = self.client.get( 29 | "/v1/ignite/deployments", 30 | ).await.unwrap(); 31 | 32 | let deployments = response["data"]["deployments"].to_owned(); 33 | 34 | return Ok(serde_json::from_value(deployments).unwrap()); 35 | } 36 | 37 | pub async fn get_deployment( 38 | &self, 39 | deployment_id: &str, 40 | ) -> Result { 41 | let response = self.client.get( 42 | format!("/v1/ignite/deployments/{}", deployment_id).as_str(), 43 | ).await.unwrap(); 44 | 45 | let deployment = response["data"]["deployment"].to_owned(); 46 | 47 | return Ok(serde_json::from_value(deployment).unwrap()); 48 | } 49 | 50 | pub async fn get_deployment_by_name( 51 | &self, 52 | deployment_name: &str, 53 | ) -> Result { 54 | let response = self.client.get( 55 | format!("/v1/ignite/deployments/search?name={}", deployment_name).as_str(), 56 | ).await.unwrap(); 57 | 58 | let deployment = response["data"]["deployment"].to_owned(); 59 | 60 | return Ok(serde_json::from_value(deployment).unwrap()); 61 | } 62 | 63 | pub async fn create_deployment( 64 | &self, 65 | config: CreateDeploymentConfig, 66 | ) -> Result { 67 | // TODO: should this be here? it is here to match js sdk typed version 68 | if !VERSIONS.contains(&config.version.as_str()) { 69 | println!("Invalid version. Valid versions are: {:?}", VERSIONS); 70 | return Err(APIError); 71 | } 72 | 73 | if (get_bytes(config.resources.ram.as_str())) <= SIX_MB_IN_BYTES { 74 | println!("Allocated memory must be greater than 6MB when creating a deployment."); 75 | return Err(APIError); 76 | } 77 | 78 | if config.volume.is_some() && config.runtime_type != RuntimeType::Stateful { 79 | println!("Cannot create a deployment with a volume that is not stateful."); 80 | return Err(APIError); 81 | } 82 | 83 | let mut data = serde_json::json!(config); 84 | 85 | // TODO: is this needed? 86 | if config.volume.is_none() { 87 | data.as_object_mut().unwrap().remove("volume"); 88 | } 89 | 90 | let response = self.client.post( 91 | "/v1/ignite/deployments", 92 | serde_json::json!(data), 93 | ).await.unwrap(); 94 | 95 | let deployment = response["data"]["deployment"].to_owned(); 96 | 97 | return Ok(serde_json::from_value(deployment).unwrap()); 98 | } 99 | 100 | pub async fn delete_deployment( 101 | &self, 102 | deployment_id: &str, 103 | ) -> Result<(), APIError> { 104 | self.client.delete( 105 | format!("/v1/ignite/deployments/{}", deployment_id).as_str(), 106 | ).await.unwrap(); 107 | 108 | return Ok(()); 109 | } 110 | 111 | // TODO: test this 112 | pub async fn patch_metadata( 113 | &self, 114 | deployment_id: &str, 115 | metadata: serde_json::Value, 116 | ) -> Result { 117 | let response = self.client.patch( 118 | format!("/v1/ignite/deployments/{}/metadata", deployment_id).as_str(), 119 | serde_json::json!(metadata), 120 | ).await.unwrap(); 121 | 122 | let deployment = response["data"]["deployment"].to_owned(); 123 | 124 | return Ok(serde_json::from_value(deployment).unwrap()); 125 | } 126 | 127 | pub async fn rollout_deployment( 128 | &self, 129 | deployment_id: &str, 130 | ) -> Result { 131 | let response = self.client.post( 132 | format!("/v1/ignite/deployments/{}/rollouts", deployment_id).as_str(), 133 | serde_json::Value::Null, 134 | ).await.unwrap(); 135 | 136 | let rollout = response["data"]["rollout"].to_owned(); 137 | 138 | return Ok(serde_json::from_value(rollout).unwrap()); 139 | } 140 | 141 | // TODO: finish this 142 | pub async fn update_deployment( 143 | &self, 144 | deployment_id: &str, 145 | config: UpdateDeploymentConfig, 146 | ) -> Result { 147 | // TODO: remove None values from config? 148 | 149 | let response = self.client.patch( 150 | format!("/v1/ignite/deployments/{}", deployment_id).as_str(), 151 | serde_json::json!(config), 152 | ).await.unwrap(); 153 | 154 | let deployment = response["data"]["deployment"].to_owned(); 155 | 156 | return Ok(serde_json::from_value(deployment).unwrap()); 157 | } 158 | 159 | pub async fn get_storage_stats( 160 | &self, 161 | deployment_id: &str, 162 | ) -> Result { 163 | let response = self.client.get( 164 | format!("/v1/ignite/deployments/{}/storage", deployment_id).as_str(), 165 | ).await.unwrap(); 166 | 167 | let storage_stats = response["data"].to_owned(); 168 | 169 | return Ok(serde_json::from_value(storage_stats).unwrap()); 170 | } 171 | 172 | pub async fn get_containers( 173 | &self, 174 | deployment_id: &str, 175 | ) -> Result, APIError> { 176 | let response = self.client.get( 177 | format!("/v1/ignite/deployments/{}/containers", deployment_id).as_str(), 178 | ).await.unwrap(); 179 | 180 | let containers = response["data"]["containers"].to_owned(); 181 | 182 | return Ok(serde_json::from_value(containers).unwrap()); 183 | } 184 | 185 | // Gateways: 186 | 187 | pub async fn get_gateways( 188 | &self, 189 | deployment_id: &str, 190 | ) -> Result, APIError> { 191 | let response = self.client.get( 192 | format!("/v1/ignite/deployments/{}/gateways", deployment_id).as_str(), 193 | ).await.unwrap(); 194 | 195 | let gateways = response["data"]["gateways"].to_owned(); 196 | 197 | return Ok(serde_json::from_value(gateways).unwrap()); 198 | } 199 | 200 | pub async fn get_gateway( 201 | &self, 202 | gateway_id: &str, 203 | ) -> Result { 204 | let response = self.client.get( 205 | format!("/v1/ignite/gateways/{}", gateway_id).as_str(), 206 | ).await.unwrap(); 207 | 208 | let gateway = response["data"]["gateway"].to_owned(); 209 | 210 | return Ok(serde_json::from_value(gateway).unwrap()); 211 | } 212 | 213 | // TODO: this 214 | pub async fn create_gateway( 215 | &self, 216 | deployment_id: &str, 217 | config: GatewayConfig, 218 | ) -> Result { 219 | let mut data = serde_json::json!({ 220 | "type": config.gateway_type, 221 | "protocol": config.protocol, 222 | "target_port": config.target_port, 223 | "name": config.name, 224 | }); 225 | 226 | match config.gateway_type { 227 | GatewayType::Internal => { 228 | if config.internal_domain.is_some() { 229 | data["internal_domain"] = serde_json::json!(config.internal_domain.unwrap()); 230 | } else { 231 | panic!("internal_domain is required for internal gateways"); 232 | } 233 | } 234 | _ => {} 235 | } 236 | 237 | let response = self.client.post( 238 | format!("/v1/ignite/deployments/{}/gateways", deployment_id).as_str(), 239 | data, 240 | ).await.unwrap(); 241 | 242 | let gateway = response["data"]["gateway"].to_owned(); 243 | 244 | return Ok(serde_json::from_value(gateway).unwrap()); 245 | } 246 | 247 | pub async fn add_domain( 248 | &self, 249 | gateway_id: &str, 250 | domain: &str, 251 | ) -> Result<(), APIError> { 252 | self.client.post( 253 | format!("/v1/ignite/gateways/{}/domains", gateway_id).as_str(), 254 | serde_json::json!({ 255 | "domain": domain, 256 | }), 257 | ).await.unwrap(); 258 | 259 | return Ok(()); 260 | } 261 | 262 | pub async fn delete_domain( 263 | &self, 264 | gateway_id: &str, 265 | ) -> Result<(), APIError> { 266 | self.client.delete( 267 | format!("/v1/ignite/gateways/{}/domains", gateway_id).as_str(), 268 | ).await.unwrap(); 269 | 270 | return Ok(()); 271 | } 272 | 273 | // TODO: check this 274 | /*pub async fn create_domain( 275 | &self, 276 | ) -> () { 277 | panic!("not implemented!"); 278 | }*/ 279 | 280 | // Health Checks: 281 | 282 | pub async fn create_healthcheck( 283 | &self, 284 | deployment_id: &str, 285 | config: CreateHealthCheckConfig, 286 | ) -> Result { 287 | let response = self.client.post( 288 | format!("/v1/ignite/deployments/{}/health-check", deployment_id).as_str(), 289 | serde_json::json!(config), 290 | ).await.unwrap(); 291 | 292 | let healthcheck = response["data"]["health_check"].to_owned(); 293 | 294 | return Ok(serde_json::from_value(healthcheck).unwrap()); 295 | } 296 | 297 | pub async fn update_healthcheck( 298 | &self, 299 | deployment_id: &str, 300 | config: UpdateHealthCheckConfig, 301 | ) -> Result<(), APIError> { 302 | let mut config = serde_json::json!(config); 303 | 304 | config = config.as_object().unwrap().iter().fold(serde_json::json!({}), |mut acc, (k, v)| { 305 | if !v.is_null() { 306 | acc[k] = v.clone(); 307 | } 308 | return acc; 309 | }); 310 | 311 | self.client.patch( 312 | format!("/v1/ignite/deployments/{}/health-check", deployment_id).as_str(), 313 | serde_json::json!(config), 314 | ).await.unwrap(); 315 | 316 | return Ok(()); 317 | } 318 | 319 | // Containers: 320 | 321 | // TODO: this 322 | pub async fn create_container( 323 | &self, 324 | ) -> () { 325 | panic!("not implemented!"); 326 | } 327 | 328 | pub async fn delete_container( 329 | &self, 330 | container_id: &str, 331 | // TODO: add options 332 | recreate: bool, 333 | // TODO: should it return this? 334 | ) -> Result, APIError> { 335 | if !recreate { 336 | self.client.delete( 337 | format!("/v1/ignite/containers/{}", container_id).as_str(), 338 | ).await.unwrap(); 339 | 340 | return Ok(None); 341 | } 342 | 343 | let response = self.client.delete_with_return( 344 | format!("/v1/ignite/containers/{}?recreate=true", container_id).as_str(), 345 | // TODO: add recreate option to query params 346 | // format!("/v1/ignite/containers/{}", container_id).as_str(), 347 | ).await.unwrap(); 348 | 349 | let container = response["data"]["container"].to_owned(); 350 | 351 | return Ok(Some(serde_json::from_value(container).unwrap())); 352 | } 353 | 354 | pub async fn start_container( 355 | &self, 356 | container_id: &str, 357 | ) -> Result<(), APIError> { 358 | self.client.put( 359 | format!("/v1/ignite/containers/{}/state", container_id).as_str(), 360 | serde_json::json!({ 361 | "preferred_state": ContainerState::Running, 362 | }), 363 | ).await.unwrap(); 364 | 365 | return Ok(()); 366 | } 367 | 368 | pub async fn stop_container( 369 | &self, 370 | container_id: &str, 371 | ) -> Result<(), APIError> { 372 | self.client.put( 373 | format!("/v1/ignite/containers/{}/state", container_id).as_str(), 374 | serde_json::json!({ 375 | "preferred_state": ContainerState::Stopped 376 | }), 377 | ).await.unwrap(); 378 | 379 | return Ok(()); 380 | } 381 | 382 | pub async fn get_container_logs( 383 | &self, 384 | container_id: &str, 385 | // TODO: add options 386 | // options: () 387 | ) -> Result, APIError> { 388 | let response = self.client.get( 389 | // TODO: add options as query params 390 | format!("/v1/ignite/containers/{}/logs?offset=0", container_id).as_str(), 391 | ).await.unwrap(); 392 | 393 | let logs = response["data"]["logs"].to_owned(); 394 | 395 | return Ok(serde_json::from_value(logs).unwrap()); 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /src/sdks/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod channels; 2 | pub mod ignite; 3 | pub mod pipe; 4 | pub mod projects; 5 | pub mod registry; 6 | pub mod users; 7 | -------------------------------------------------------------------------------- /src/sdks/pipe.rs: -------------------------------------------------------------------------------- 1 | use crate::{APIClient, APIError}; 2 | use crate::types::pipe::{DeliveryProtocol, IngestProtocol, Region, Room, RoomOptions, RoomState}; 3 | 4 | pub struct Pipe { 5 | pub token: String, 6 | pub client: APIClient, 7 | } 8 | 9 | impl Pipe { 10 | pub fn new( 11 | token: &str, 12 | ) -> Pipe { 13 | return Pipe { 14 | token: token.to_owned(), 15 | client: APIClient::new(token), 16 | }; 17 | } 18 | 19 | // Rooms: 20 | 21 | pub async fn get_rooms( 22 | &self 23 | ) -> Result, APIError> { 24 | let response = self.client.get( 25 | "/v1/pipe/rooms" 26 | ).await.unwrap(); 27 | 28 | let rooms = response["data"]["rooms"].to_owned(); 29 | 30 | return Ok(serde_json::from_value(rooms).unwrap()); 31 | } 32 | 33 | // TODO: check this 34 | /*pub async fn get_room( 35 | &self 36 | ) -> () { 37 | panic!("not implemented!"); 38 | }*/ 39 | 40 | pub async fn create( 41 | &self, 42 | name: &str, 43 | options: RoomOptions, 44 | ) -> Result { 45 | let mut config = serde_json::json!({ 46 | "name": name, 47 | 48 | "ingest_protocol": options.ingest_protocol, 49 | "region": Region::USEast1, 50 | 51 | "ephemeral": options.ephemeral, 52 | 53 | "delivery_protocols": options.delivery_protocols, 54 | // "llhls_config": options.hls_config, 55 | }); 56 | 57 | if !options.hls_config.is_none() { 58 | config["llhls_config"] = serde_json::json!(options.hls_config.unwrap()); 59 | } 60 | 61 | let response = self.client.post( 62 | "/v1/pipe/rooms", 63 | config, 64 | ).await.unwrap(); 65 | 66 | println!("response: {:?}", response); 67 | 68 | let mut room = response["data"]["room"].clone(); 69 | room["state"] = serde_json::json!(RoomState::Offline); 70 | 71 | return Ok(serde_json::from_value(room).unwrap()); 72 | } 73 | 74 | pub async fn delete( 75 | &self, 76 | room_id: &str, 77 | ) -> Result<(), APIError> { 78 | return self.client.delete( 79 | format!("/v1/pipe/rooms/{}", room_id).as_str() 80 | ).await; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/sdks/projects.rs: -------------------------------------------------------------------------------- 1 | use crate::client::APIClient; 2 | use crate::types::APIError; 3 | use crate::types::projects::{Member, Secret, Token}; 4 | 5 | pub struct Projects { 6 | pub token: String, 7 | pub client: APIClient, 8 | } 9 | 10 | impl Projects { 11 | pub fn new( 12 | token: &str 13 | ) -> Projects { 14 | return Projects { 15 | token: token.to_owned(), 16 | client: APIClient::new(token), 17 | }; 18 | } 19 | 20 | // Projects: 21 | 22 | pub async fn get_members( 23 | &self, 24 | ) -> Result, APIError> { 25 | let response = self.client.get( 26 | "/v1/projects/@this/members" 27 | ).await.unwrap(); 28 | 29 | let members = response["data"]["members"].to_owned(); 30 | 31 | return Ok(serde_json::from_value(members).unwrap()); 32 | } 33 | 34 | pub async fn get_current_member( 35 | &self, 36 | // TODO: add this 37 | // project_id: &str, 38 | ) -> Result { 39 | let response = self.client.get( 40 | // format!("/v1/projects/{}/members/@me", project_id).as_str() 41 | "/v1/projects/@this/members/@me" 42 | ).await.unwrap(); 43 | 44 | let member = response["data"]["member"].to_owned(); 45 | 46 | return Ok(serde_json::from_value(member).unwrap()); 47 | } 48 | 49 | // TODO: fix tokens (ask hop) 50 | // Tokens: 51 | 52 | pub async fn get_tokens( 53 | &self, 54 | ) -> Result, APIError> { 55 | let response = self.client.get( 56 | "/v1/projects/@this/tokens" 57 | ).await.unwrap(); 58 | 59 | let tokens = response["data"]["tokens"].to_owned(); 60 | 61 | return Ok(serde_json::from_value(tokens).unwrap()); 62 | } 63 | 64 | pub async fn create_token( 65 | &self, 66 | // TODO: create util to create flags 67 | flags: i32, 68 | ) -> Result { 69 | let response = self.client.post( 70 | "/v1/projects/@this/tokens", 71 | // serde_json::json!(flags), 72 | serde_json::json!({ 73 | "flags": flags, 74 | }), 75 | ).await.unwrap(); 76 | 77 | let token = response["data"]["token"].to_owned(); 78 | 79 | return Ok(serde_json::from_value(token).unwrap()); 80 | } 81 | 82 | pub async fn delete_token( 83 | &self, 84 | id: &str, 85 | ) -> Result<(), APIError> { 86 | self.client.delete( 87 | format!("/v1/projects/@this/tokens/{}", id).as_str(), 88 | ).await.unwrap(); 89 | 90 | return Ok(()); 91 | } 92 | 93 | // Secrets: 94 | 95 | pub async fn get_secrets( 96 | &self, 97 | ) -> Result, APIError> { 98 | let response = self.client.get( 99 | "/v1/projects/@this/secrets" 100 | ).await.unwrap(); 101 | 102 | let secrets = response["data"]["secrets"].to_owned(); 103 | 104 | return Ok(serde_json::from_value(secrets).unwrap()); 105 | } 106 | 107 | pub async fn create_secret( 108 | &self, 109 | name: &str, 110 | value: String, 111 | ) -> Result { 112 | let response = self.client.put_raw( 113 | format!("/v1/projects/@this/secrets/{}", name).as_str(), 114 | value, 115 | ).await.unwrap(); 116 | 117 | let secret = response["data"]["secret"].to_owned(); 118 | 119 | return Ok(serde_json::from_value(secret).unwrap()); 120 | } 121 | 122 | pub async fn delete_secret( 123 | &self, 124 | id: &str, 125 | ) -> Result<(), APIError> { 126 | self.client.delete( 127 | format!("/v1/projects/@this/secrets/{}", id).as_str(), 128 | ).await.unwrap(); 129 | 130 | return Ok(()); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/sdks/registry.rs: -------------------------------------------------------------------------------- 1 | use crate::client::APIClient; 2 | use crate::types::APIError; 3 | use crate::types::registry::Image; 4 | 5 | pub struct Registry { 6 | pub token: String, 7 | pub client: APIClient, 8 | } 9 | 10 | impl Registry { 11 | pub fn new( 12 | token: &str, 13 | ) -> Registry { 14 | return Registry { 15 | token: token.to_owned(), 16 | client: APIClient::new(token), 17 | }; 18 | } 19 | 20 | // Images: 21 | 22 | pub async fn get_images( 23 | &self 24 | // TODO: add image return here 25 | // ) -> Result, APIError> { 26 | ) -> Result, APIError> { 27 | let response = self.client.get( 28 | "/v1/registry/images" 29 | ).await.unwrap(); 30 | 31 | let images = response["data"]["images"].clone(); 32 | 33 | return Ok(serde_json::from_value(images).unwrap()); 34 | } 35 | 36 | pub async fn delete_image( 37 | &self, 38 | image: &str, 39 | ) -> Result<(), APIError> { 40 | self.client.delete( 41 | format!("/v1/registry/images/{}", image).as_str(), 42 | ).await.unwrap(); 43 | 44 | return Ok(()); 45 | } 46 | 47 | pub async fn get_manifest( 48 | &self, 49 | image: &str, 50 | ) -> Result, APIError> { 51 | let response = self.client.get( 52 | format!("/v1/registry/{}/manifest", image).as_str(), 53 | ).await.unwrap(); 54 | 55 | let manifest = response["data"]["image"].clone(); 56 | 57 | return Ok(serde_json::from_value(manifest).unwrap()); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/sdks/users.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | 3 | use crate::client::APIClient; 4 | use crate::types::APIError; 5 | use crate::types::users::{Pat, User}; 6 | 7 | pub struct Users { 8 | pub token: String, 9 | pub client: APIClient, 10 | } 11 | 12 | impl Users { 13 | pub fn new( 14 | token: &str 15 | ) -> Users { 16 | return Users { 17 | token: token.to_owned(), 18 | client: APIClient::new(token), 19 | }; 20 | } 21 | 22 | // Me: 23 | 24 | pub async fn get_me( 25 | &self, 26 | ) -> Result { 27 | let response = self.client.get( 28 | "/v1/users/@me" 29 | ).await.unwrap(); 30 | 31 | let me = response["data"]["user"].clone(); 32 | 33 | return Ok(serde_json::from_value(me).unwrap()); 34 | } 35 | 36 | // Pats: 37 | 38 | pub async fn get_pats( 39 | &self, 40 | ) -> Result, APIError> { 41 | let response = self.client.get( 42 | "/v1/users/@me/pats" 43 | ).await.unwrap(); 44 | 45 | let pats = response["data"]["pats"].clone(); 46 | 47 | return Ok(serde_json::from_value(pats).unwrap()); 48 | } 49 | 50 | pub async fn create_pat( 51 | &self, 52 | name: &str, 53 | ) -> Result { 54 | // TODO: fix invalid route error (actually just dumb) 55 | let response = self.client.post( 56 | "/v1/users/@me/pats", 57 | // serde_json::json!(name), 58 | serde_json::json!({ 59 | "name": name, 60 | }), 61 | ).await.unwrap(); 62 | 63 | let pat = response["data"]["pat"].clone(); 64 | 65 | return Ok(serde_json::from_value(pat).unwrap()); 66 | } 67 | 68 | pub async fn delete_pat( 69 | &self, 70 | id: &str, 71 | ) -> Result<(), APIError> { 72 | // TODO: fix invalid route error (actually just dumb) 73 | self.client.delete( 74 | format!("/v1/users/@me/pats/{}", id).as_str() 75 | ).await.unwrap(); 76 | 77 | return Ok(()); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/types/channels.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Debug, Serialize, Deserialize)] 6 | pub enum ChannelType { 7 | #[serde(rename = "private")] 8 | Private, 9 | #[serde(rename = "public")] 10 | Public, 11 | #[serde(rename = "unprotected")] 12 | Unprotected, 13 | } 14 | 15 | // TODO: what is this? 16 | // #[derive(Debug, Serialize, Deserialize)] 17 | // pub struct ChannelState {} 18 | 19 | #[derive(Debug, Serialize, Deserialize)] 20 | pub struct Project {} 21 | 22 | #[derive(Debug, Serialize, Deserialize)] 23 | pub struct ChannelToken { 24 | /// The ID for the token 25 | pub id: String, 26 | /// State for this token 27 | pub state: HashMap, 28 | // TODO: check this 29 | // /// The project this channel token is associated with 30 | // project_id: String, 31 | /// If this token is currently online (e.g. active heartbeat and connected to leap) 32 | pub is_online: bool, 33 | // TODO: check these 34 | pub expires_at: Option, 35 | pub created_at: String, 36 | } 37 | 38 | #[derive(Debug, Serialize, Deserialize)] 39 | pub struct Channel { 40 | /// The ID of the channel 41 | pub id: String, 42 | // /// The project it is associated with 43 | // pub project: Project, 44 | /// State metadata 45 | pub state: HashMap, 46 | // /// Capabilities of the channel 47 | // pub capabilities: i64, 48 | /// When this channel was created 49 | pub created_at: String, 50 | /// The type of this channel 51 | #[serde(rename = "type")] 52 | pub channel_type: ChannelType, 53 | } 54 | 55 | impl Channel { 56 | pub fn set_state(&self) { 57 | println!("Setting channel state"); 58 | panic!("not implemented!") 59 | } 60 | 61 | pub fn patch_state(&self) { 62 | println!("Patching channel state"); 63 | panic!("not implemented!") 64 | } 65 | 66 | pub fn subscribe_tokens(&self) { 67 | println!("Subscribing tokens to channel"); 68 | panic!("not implemented!") 69 | } 70 | 71 | pub fn subscribe_token(&self) { 72 | println!("Subscribing token to channel"); 73 | panic!("not implemented!") 74 | } 75 | 76 | pub fn publish_message(&self) { 77 | println!("Publishing a channel message"); 78 | panic!("not implemented!") 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/types/ignite.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use crate::types::pipe::Region; 6 | 7 | #[derive(Debug, Serialize, Deserialize)] 8 | pub struct Resources { 9 | pub vcpu: f64, 10 | pub ram: String, 11 | // TODO: use array instead of vector? 12 | pub vgpu: Option>, 13 | } 14 | 15 | impl Resources { 16 | pub fn new( 17 | vcpu: f64, 18 | ram: &str, 19 | vgpu: Option>, 20 | ) -> Resources { 21 | return Resources { 22 | vcpu, 23 | // TODO: evaluate ram validity here as well? 24 | ram: ram.to_owned(), 25 | vgpu, 26 | }; 27 | } 28 | } 29 | 30 | #[derive(Debug, Serialize, Deserialize)] 31 | /// * Types for supported GPU 32 | pub enum VgpuType { 33 | #[serde(rename = "a400")] 34 | A400, 35 | } 36 | 37 | #[derive(Debug, Serialize, Deserialize)] 38 | pub struct Vgpu { 39 | #[serde(rename = "type")] 40 | pub vgpu_type: VgpuType, 41 | pub count: i64, 42 | } 43 | 44 | impl Vgpu { 45 | pub fn new( 46 | vgpu_type: VgpuType, 47 | count: i64, 48 | ) -> Vgpu { 49 | return Vgpu { 50 | vgpu_type, 51 | count, 52 | }; 53 | } 54 | } 55 | 56 | #[derive(Debug, Serialize, Deserialize)] 57 | /// Container state is relatively self-explanatory. 58 | /// It describes what the container is currently doing. 59 | pub enum ContainerState { 60 | #[serde(rename = "pending")] 61 | /// The container is pending creation 62 | Pending, 63 | #[serde(rename = "running")] 64 | /// The container is running 65 | Running, 66 | #[serde(rename = "stopped")] 67 | /// The container is stopped 68 | Stopped, 69 | #[serde(rename = "failed")] 70 | /// The container's entrypoint failed (e.g. exited with a non-zero exit code) 71 | Failed, 72 | #[serde(rename = "terminating")] 73 | /// The container is being deleted 74 | Terminating, 75 | #[serde(rename = "exited")] 76 | /// The container exited (e.g. with a zero exit code) 77 | Exited, 78 | } 79 | 80 | #[derive(Debug, Serialize, Deserialize)] 81 | pub enum ContainerStrategy { 82 | #[serde(rename = "manual")] 83 | Manual, 84 | } 85 | 86 | #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] 87 | /// Runtime types are used to describe the type of a deployment or container 88 | pub enum RuntimeType { 89 | #[serde(rename = "ephemeral")] 90 | /// Ephemeral deployments/containers are sort of fire and forget. 91 | /// Containers won't restart if they exit but they can still be terminated programmatically. 92 | Ephemeral, 93 | #[serde(rename = "persistent")] 94 | /// Persistent deployments/containers will restart if they exit. 95 | /// They can also be started and stopped programmatically. 96 | Persistent, 97 | #[serde(rename = "stateful")] 98 | /// Stateful deployments/containers can only run one container at a time, and will have a 99 | /// persistent volume attached. 100 | Stateful, 101 | } 102 | 103 | #[derive(Debug, Serialize, Deserialize)] 104 | /// * Restart policy for deployments 105 | pub enum RestartPolicy { 106 | #[serde(rename = "never")] 107 | Never, 108 | #[serde(rename = "always")] 109 | Always, 110 | #[serde(rename = "on-failure")] 111 | OnFailure, 112 | } 113 | 114 | #[derive(Debug, Serialize, Deserialize)] 115 | pub struct Auth { 116 | pub username: String, 117 | pub password: String, 118 | } 119 | 120 | impl Auth { 121 | pub fn new( 122 | username: &str, 123 | password: &str, 124 | ) -> Auth { 125 | return Auth { 126 | username: username.to_owned(), 127 | password: password.to_owned(), 128 | }; 129 | } 130 | } 131 | 132 | #[derive(Debug, Serialize, Deserialize)] 133 | pub struct ImageGHRepo { 134 | pub repo_id: i64, 135 | pub full_name: String, 136 | pub branch: String, 137 | } 138 | 139 | #[derive(Debug, Serialize, Deserialize)] 140 | pub struct Image { 141 | pub name: Option, 142 | pub auth: Option, 143 | pub gh_repo: Option, 144 | } 145 | 146 | impl Image { 147 | pub fn new( 148 | name: Option<&str>, 149 | auth: Option, 150 | gh_repo: Option, 151 | ) -> Image { 152 | return Image { 153 | name: name.map(|s| s.to_owned()), 154 | auth, 155 | gh_repo, 156 | }; 157 | } 158 | } 159 | 160 | #[derive(Debug, Serialize, Deserialize)] 161 | pub struct DeploymentLog { 162 | pub nonce: String, 163 | pub timestamp: String, 164 | pub level: String, 165 | pub message: String, 166 | } 167 | 168 | #[derive(Debug, Serialize, Deserialize)] 169 | pub enum GatewayType { 170 | #[serde(rename = "internal")] 171 | Internal, 172 | #[serde(rename = "external")] 173 | External, 174 | } 175 | 176 | #[derive(Debug, Serialize, Deserialize)] 177 | pub enum DomainState { 178 | #[serde(rename = "pending")] 179 | Pending, 180 | #[serde(rename = "valid_cname")] 181 | ValidCName, 182 | #[serde(rename = "ssl_active")] 183 | SSLActive, 184 | } 185 | 186 | #[derive(Debug, Serialize, Deserialize)] 187 | pub struct Domain { 188 | pub id: String, 189 | pub domain: String, 190 | pub state: DomainState, 191 | pub created_at: String, 192 | pub redirect: String, 193 | } 194 | 195 | #[derive(Debug, Serialize, Deserialize)] 196 | pub enum GatewayProtocol { 197 | #[serde(rename = "http")] 198 | HTTP, 199 | } 200 | 201 | #[derive(Debug, Serialize, Deserialize)] 202 | pub struct GatewayConfig { 203 | #[serde(rename = "type")] 204 | pub gateway_type: GatewayType, 205 | pub protocol: GatewayProtocol, 206 | pub target_port: i64, 207 | pub name: String, 208 | pub internal_domain: Option, 209 | } 210 | 211 | #[derive(Debug, Serialize, Deserialize)] 212 | pub struct Gateway { 213 | pub id: String, 214 | #[serde(rename = "type")] 215 | pub gateway_type: GatewayType, 216 | pub name: Option, 217 | pub protocol: Option, 218 | pub deployment_id: String, 219 | pub created_at: String, 220 | pub hopsh_domain: Option, 221 | pub hopsh_domain_enabled: Option, 222 | pub internal_domain: Option, 223 | pub target_port: Option, 224 | pub primary: Option, 225 | // TODO: this is not in GET/gateways response 226 | // pub domains: Vec, 227 | } 228 | 229 | impl Gateway { 230 | pub fn add_domain(&self) { 231 | println!("Adding gateway domain"); 232 | panic!("not implemented!") 233 | } 234 | 235 | pub fn delete_domain(&self) { 236 | println!("Deleting gateway domain"); 237 | panic!("not implemented!") 238 | } 239 | } 240 | 241 | // TODO: this 242 | #[derive(Debug, Serialize, Deserialize)] 243 | pub struct PatchDeploymentMetadata { 244 | // TODO: add this 245 | // pub container_port_mappings: Option, string[]>>, 246 | pub ignored_boarding: Option, 247 | pub created_from_preset: Option, 248 | pub created_first_gateway: Option, 249 | } 250 | 251 | // TODO: this 252 | #[derive(Debug, Serialize, Deserialize)] 253 | pub struct Build {} 254 | 255 | #[derive(Debug, Serialize, Deserialize)] 256 | pub struct BuildSettings { 257 | pub root_directory: Option, 258 | } 259 | 260 | // TODO: check this 261 | #[derive(Debug, Serialize, Deserialize)] 262 | pub struct DeploymentMetaData { 263 | pub root_directory: Option, 264 | } 265 | 266 | #[derive(Debug, Serialize, Deserialize)] 267 | pub struct DeploymentOld { 268 | pub id: String, 269 | pub name: String, 270 | pub container_count: i64, 271 | pub created_at: String, 272 | pub metadata: Option, 273 | pub build_cache_enabled: bool, 274 | pub build_settings: BuildSettings, 275 | } 276 | 277 | // TODO: this 278 | // #[derive(Debug, Serialize, Deserialize)] 279 | pub struct DeploymentRollout {} 280 | 281 | #[derive(Debug, Serialize, Deserialize)] 282 | pub struct Deployment { 283 | /// The ID of the deployment 284 | pub id: String, 285 | /// The name of the deployment 286 | pub name: String, 287 | /// The amount of containers this deployment is currently running 288 | pub container_count: i64, 289 | /// The time this deployment was created at 290 | pub created_at: String, 291 | /// The config for this deployment 292 | pub config: DeploymentConfig, 293 | /// Current active rollout for deployment 294 | pub active_rollout: Option, 295 | /// Current active build for deployment 296 | pub active_build: Option, 297 | /// The ID of the build currently being used in production by this deployment. 298 | /// This will change if another build has been promoted to production. 299 | pub build_id: Option, 300 | // TODO: this 301 | // /// Current active rollout for deployment 302 | // pub latest_rollout: Option, 303 | pub target_container_count: i64, 304 | /// The amount of containers in the running state 305 | pub running_container_count: Option, 306 | // TODO: this 307 | // /// Metadata for deployment 308 | // pub metadata: Option, 309 | /// Build cache settings for deployment 310 | pub build_cache_enabled: bool, 311 | /// Build settings for deployment 312 | pub build_settings: Option, 313 | } 314 | 315 | impl Deployment { 316 | pub fn get_containers(&self) { 317 | println!("Getting deployment containers"); 318 | panic!("not implemented!") 319 | } 320 | 321 | pub fn create_container(&self) { 322 | println!("Creating deployment container"); 323 | panic!("not implemented!") 324 | } 325 | 326 | pub fn create_gateway(&self) { 327 | println!("Creating deployment gateway"); 328 | panic!("not implemented!") 329 | } 330 | 331 | pub fn get_storage_stats(&self) { 332 | println!("Getting deployment storage stats"); 333 | panic!("not implemented!") 334 | } 335 | 336 | pub fn delete(&self) { 337 | println!("Deleting deployment"); 338 | panic!("not implemented!") 339 | } 340 | } 341 | 342 | #[derive(Debug, Serialize, Deserialize)] 343 | pub enum VolumeFormat { 344 | #[serde(rename = "ext4")] 345 | EXT4, 346 | #[serde(rename = "xfs")] 347 | XFS, 348 | } 349 | 350 | #[derive(Debug, Serialize, Deserialize)] 351 | pub struct VolumeDefinition { 352 | /// The format of the volume 353 | pub fs: VolumeFormat, 354 | // TODO: better type? 355 | /// The size of the volume in bytes 356 | pub size: String, 357 | // TODO: check if hop accepts my hop-js sdk fix 358 | /// The mount point of the volume 359 | pub mountpath: String, 360 | } 361 | 362 | // TODO: check this 363 | #[derive(Debug, Serialize, Deserialize)] 364 | pub struct UpdateDeploymentConfig { 365 | pub name: Option, 366 | pub container_strategy: Option, 367 | #[serde(rename = "type")] 368 | pub runtime_type: Option, 369 | pub version: Option, 370 | pub cmd: Option>, 371 | pub image: Option, 372 | pub env: Option>, 373 | pub resources: Option, 374 | pub restart_policy: Option, 375 | // pub volume: Option>, 376 | pub volume: Option, 377 | // pub volume: Option>, 378 | pub entrypoint: Option>, 379 | } 380 | 381 | #[derive(Debug, Serialize, Deserialize)] 382 | pub struct CreateDeploymentConfig { 383 | pub name: String, 384 | pub container_strategy: ContainerStrategy, 385 | #[serde(rename = "type")] 386 | pub runtime_type: RuntimeType, 387 | pub version: String, 388 | pub cmd: Option>, 389 | pub image: Image, 390 | pub env: Option>, 391 | pub resources: Resources, 392 | pub restart_policy: RestartPolicy, 393 | // pub volume: Option>, 394 | pub volume: Option, 395 | // pub volume: Option>, 396 | pub entrypoint: Option>, 397 | } 398 | 399 | impl CreateDeploymentConfig { 400 | pub fn new( 401 | name: &str, 402 | // container_strategy: ContainerStrategy, 403 | runtime_type: RuntimeType, 404 | version: &str, 405 | cmd: Option>, 406 | image: Image, 407 | env: Option>, 408 | resources: Resources, 409 | restart_policy: RestartPolicy, 410 | // volume: Option<&str>, 411 | // TODO: check this still works 412 | volume: Option, 413 | entrypoint: Option>, 414 | ) -> CreateDeploymentConfig { 415 | 416 | // TODO: perform some light validation here? 417 | // such as version, volume, ram size 418 | 419 | return CreateDeploymentConfig { 420 | name: name.to_owned(), 421 | container_strategy: ContainerStrategy::Manual, 422 | runtime_type: runtime_type.clone(), 423 | version: version.to_owned(), 424 | cmd, 425 | image, 426 | env: env.map(|e| e.into_iter().map(|(k, v)| (k.to_owned(), v.to_owned())).collect()), 427 | resources, 428 | restart_policy, 429 | /*volume: match runtime_type { 430 | RuntimeType::Stateful => volume.map(|v| v.to_owned()), 431 | _ => None, 432 | },*/ 433 | // TODO: check this still works 434 | volume, 435 | // volume: volume.map(|e| e.into_iter().map(|(k, v)| (k.to_owned(), v.to_owned())).collect()), 436 | entrypoint: entrypoint.map(|e| e.into_iter().map(|e| e.to_owned()).collect()), 437 | }; 438 | } 439 | } 440 | 441 | // TODO: remove option from hashmap and vec fields in struct but keep in constructor 442 | 443 | // TODO: this is the returned config from the GET deployment endpoint, not the creation config 444 | #[derive(Debug, Serialize, Deserialize)] 445 | pub struct DeploymentConfig { 446 | // name: String, 447 | container_strategy: ContainerStrategy, 448 | #[serde(rename = "type")] 449 | runtime_type: RuntimeType, 450 | version: String, 451 | cmd: Option>, 452 | image: Image, 453 | env: Option>, 454 | resources: Resources, 455 | restart_policy: RestartPolicy, 456 | volume: Option>, 457 | // volume: Option>, 458 | entrypoint: Option>, 459 | } 460 | 461 | impl DeploymentConfig { 462 | pub fn new( 463 | name: &str, 464 | // container_strategy: ContainerStrategy, 465 | runtime_type: RuntimeType, 466 | // version: String, 467 | cmd: Option>, 468 | image: Image, 469 | env: Option>, 470 | resources: Resources, 471 | restart_policy: RestartPolicy, 472 | // volume: Option<&str>, 473 | volume: Option>, 474 | entrypoint: Option>, 475 | ) -> DeploymentConfig { 476 | return DeploymentConfig { 477 | // name: name.to_owned(), 478 | container_strategy: ContainerStrategy::Manual, 479 | runtime_type: runtime_type.clone(), 480 | version: "12-12-2022".to_owned(), 481 | cmd, 482 | image, 483 | env: env.map(|e| e.into_iter().map(|(k, v)| (k.to_owned(), v.to_owned())).collect()), 484 | resources, 485 | restart_policy, 486 | /*volume: match runtime_type { 487 | RuntimeType::Stateful => volume.map(|v| v.to_owned()), 488 | _ => None, 489 | },*/ 490 | volume: volume.map(|e| e.into_iter().map(|(k, v)| (k.to_owned(), v.to_owned())).collect()), 491 | entrypoint: entrypoint.map(|e| e.into_iter().map(|e| e.to_owned()).collect()), 492 | }; 493 | } 494 | } 495 | 496 | #[derive(Debug, Serialize, Deserialize)] 497 | pub struct Container { 498 | /// The ID of the container 499 | pub id: String, 500 | /// The time this container was created 501 | pub created_at: String, 502 | /// The region this container runs in 503 | pub region: Region, 504 | /// The state this container is in 505 | pub state: ContainerState, 506 | pub deployment_id: String, 507 | pub internal_ip: Option, 508 | // TODO: fix uptime to struct with `last_start` 509 | /// Information about uptime/downtime for this container 510 | pub uptime: Option, 511 | #[serde(rename = "type")] 512 | /// The type of this container 513 | pub runtime_type: RuntimeType, 514 | } 515 | 516 | // TODO: finish this 517 | #[derive(Debug, Serialize, Deserialize)] 518 | pub enum HealthCheckType { 519 | #[serde(rename = "liveness")] 520 | Liveness, 521 | } 522 | 523 | #[derive(Debug, Serialize, Deserialize)] 524 | pub struct HealthCheck { 525 | pub id: String, 526 | pub protocol: HealthCheckProtocol, 527 | pub path: String, 528 | pub port: i64, 529 | pub interval: i64, 530 | pub timeout: i64, 531 | pub initial_delay: i64, 532 | pub max_retries: i64, 533 | pub created_at: String, 534 | pub deployment_id: String, 535 | pub success_threshold: i64, 536 | #[serde(rename = "type")] 537 | pub healthcheck_type: HealthCheckType, 538 | } 539 | 540 | #[derive(Debug, Serialize, Deserialize)] 541 | pub enum HealthCheckProtocol { 542 | #[serde(rename = "http")] 543 | HTTP, 544 | } 545 | 546 | #[derive(Debug, Serialize, Deserialize)] 547 | pub struct CreateHealthCheckConfig { 548 | pub protocol: HealthCheckProtocol, 549 | pub path: String, 550 | pub port: i64, 551 | pub interval: i64, 552 | pub timeout: i64, 553 | pub initial_delay: i64, 554 | pub max_retries: i64, 555 | } 556 | 557 | #[derive(Debug, Serialize, Deserialize)] 558 | pub struct UpdateHealthCheckConfig { 559 | pub protocol: Option, 560 | pub path: Option, 561 | pub port: Option, 562 | pub interval: Option, 563 | pub timeout: Option, 564 | pub initial_delay: Option, 565 | pub max_retries: Option, 566 | } 567 | 568 | #[derive(Debug, Serialize, Deserialize)] 569 | pub struct HealthCheckConfig { 570 | pub protocol: HealthCheckProtocol, 571 | pub path: String, 572 | pub port: i64, 573 | pub interval: i64, 574 | pub timeout: i64, 575 | pub initial_delay: i64, 576 | pub max_retries: i64, 577 | } 578 | 579 | // TODO: this 580 | #[derive(Debug, Serialize, Deserialize)] 581 | pub struct BuildCacheStats {} 582 | 583 | #[derive(Debug, Serialize, Deserialize)] 584 | pub struct VolumeStats { 585 | pub provisioned_size: i64, 586 | pub used_size: i64, 587 | } 588 | 589 | #[derive(Debug, Serialize, Deserialize)] 590 | pub struct StorageStats { 591 | pub build_cache: Option, 592 | pub volume: VolumeStats, 593 | } 594 | 595 | #[derive(Debug, Serialize, Deserialize)] 596 | pub struct Rollout { 597 | pub count: i64, 598 | pub created_at: String, 599 | pub deployment_id: String, 600 | pub id: String, 601 | pub state: ContainerState, 602 | } 603 | -------------------------------------------------------------------------------- /src/types/mod.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | pub mod channels; 4 | pub mod ignite; 5 | pub mod pipe; 6 | pub mod projects; 7 | pub mod registry; 8 | pub mod users; 9 | 10 | // TODO: implement error message 11 | #[derive(Debug, Serialize, Deserialize)] 12 | pub struct APIError; 13 | 14 | -------------------------------------------------------------------------------- /src/types/pipe.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Serialize, Deserialize)] 4 | pub struct Room { 5 | /// The ID of this stream 6 | pub id: String, 7 | /// The name of this room 8 | pub name: String, 9 | /// The unix timestamp of when this stream was created 10 | pub created_at: String, 11 | /// Protocol you can stream with 12 | pub ingest_protocol: IngestProtocol, 13 | /// Protocols that are supported by this room to the client 14 | pub delivery_protocols: Vec, 15 | /// A join token to subscribe into this room 16 | pub join_token: String, 17 | /// The region that the stream url is located in 18 | pub ingest_region: Region, 19 | /// The URL that you can stream to 20 | pub ingest_endpoint: String, 21 | /// The state of the stream currently 22 | pub state: RoomState, 23 | } 24 | 25 | impl Room { 26 | pub fn delete(&self) { 27 | println!("Deleting a room"); 28 | panic!("not implemented!"); 29 | } 30 | } 31 | 32 | #[derive(Debug, Serialize, Deserialize)] 33 | pub struct RoomOptions { 34 | pub delivery_protocols: Vec, 35 | pub ephemeral: bool, 36 | pub ingest_protocol: IngestProtocol, 37 | pub hls_config: Option, 38 | } 39 | 40 | #[derive(Debug, Serialize, Deserialize)] 41 | pub struct HLSConfig { 42 | pub wcl_delay: i64, 43 | pub artificial_id: i64, 44 | pub max_playout_bitrate_preset: String, 45 | } 46 | 47 | #[derive(Debug, Serialize, Deserialize)] 48 | pub enum IngestProtocol { 49 | #[serde(rename = "rtmp")] 50 | RTMP, 51 | } 52 | 53 | #[derive(Debug, Serialize, Deserialize)] 54 | pub enum DeliveryProtocol { 55 | #[serde(rename = "webrtc")] 56 | WebRTC, 57 | // TODO: check with hop 58 | // #[serde(rename = "hls")] 59 | // HLS, 60 | } 61 | 62 | #[derive(Debug, Serialize, Deserialize)] 63 | pub enum Region { 64 | #[serde(rename = "us-east-1")] 65 | USEast1, 66 | } 67 | 68 | #[derive(Debug, Serialize, Deserialize)] 69 | pub enum RoomState { 70 | #[serde(rename = "live")] 71 | Live, 72 | #[serde(rename = "offline")] 73 | Offline, 74 | } 75 | -------------------------------------------------------------------------------- /src/types/projects.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Serialize, Deserialize)] 4 | pub struct MemberRole { 5 | /// The ID of the role 6 | pub id: String, 7 | /// The name of the role 8 | pub name: String, 9 | /// The flags for this role 10 | pub flags: i64, 11 | } 12 | 13 | #[derive(Debug, Serialize, Deserialize)] 14 | pub struct Member { 15 | /// The ID of the project member 16 | pub id: String, 17 | /// The date that this member joined the project 18 | pub joined_at: String, 19 | /// If user has multi-factor authentication enabled. 20 | pub mfa_enabled: bool, 21 | /// The role that this member has in a project 22 | pub role: MemberRole, 23 | // TODO: check these 24 | pub username: String, 25 | pub name: String, 26 | } 27 | 28 | #[derive(Debug, Serialize, Deserialize)] 29 | pub struct Secret { 30 | pub id: String, 31 | pub name: String, 32 | pub digest: String, 33 | pub created_at: String, 34 | } 35 | 36 | // TODO: check this 37 | #[derive(Debug, Serialize, Deserialize)] 38 | pub struct Token {} 39 | 40 | // TODO: check this 41 | #[derive(Debug, Serialize, Deserialize)] 42 | pub struct Project { 43 | /// The ID of the project 44 | pub id: String, 45 | /// The name of the project 46 | pub name: String, 47 | /// The tier this project is 48 | pub tier: String, 49 | /// The time this project was created at 50 | pub created_at: String, 51 | /// An icon for this project 52 | pub icon: Option, 53 | /// The registry namespace for this project 54 | pub namespace: String, 55 | /// The type of this project. Either regular or personal 56 | #[serde(rename = "type")] 57 | pub project_type: ProjectType, 58 | pub default_quotas: DefaultQuotas, 59 | pub quota_overrides: QuotaOverrides, 60 | pub quota_usage: QuotaUsage, 61 | } 62 | 63 | // TODO: check this 64 | #[derive(Debug, Serialize, Deserialize)] 65 | pub enum ProjectTier { 66 | #[serde(rename = "free")] 67 | Free, 68 | #[serde(rename = "paid")] 69 | Paid, 70 | } 71 | 72 | // TODO: check this 73 | #[derive(Debug, Serialize, Deserialize)] 74 | pub enum ProjectType { 75 | /// A standard project type 76 | #[serde(rename = "regular")] 77 | Regular, 78 | /// A personal project are created when you register an account 79 | #[serde(rename = "personal")] 80 | Personal, 81 | } 82 | 83 | #[derive(Debug, Serialize, Deserialize)] 84 | pub struct DefaultQuotas { 85 | pub vcpu: i64, 86 | pub ram: i64, 87 | pub volume: i64, 88 | } 89 | 90 | #[derive(Debug, Serialize, Deserialize)] 91 | pub struct QuotaOverrides {} 92 | 93 | #[derive(Debug, Serialize, Deserialize)] 94 | pub struct QuotaUsage { 95 | pub vcpu: i64, 96 | pub ram: i64, 97 | pub volume: i64, 98 | } 99 | -------------------------------------------------------------------------------- /src/types/registry.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | // TODO: check this 4 | #[derive(Debug, Serialize, Deserialize)] 5 | pub struct Image {} 6 | 7 | #[derive(Debug, Serialize, Deserialize)] 8 | pub struct Manifest { 9 | pub tag: String, 10 | pub digest: Digest, 11 | } 12 | 13 | #[derive(Debug, Serialize, Deserialize)] 14 | pub struct Digest { 15 | pub digest: String, 16 | pub size: i64, 17 | pub uploaded: String, 18 | } 19 | -------------------------------------------------------------------------------- /src/types/users.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Serialize, Deserialize)] 4 | pub struct User { 5 | /// The ID of the user 6 | pub id: String, 7 | /// The name of the user. 8 | pub name: String, 9 | /// A unique username for the user 10 | pub username: String, 11 | /// The email of the user 12 | pub email: String, 13 | // TODO: check these (only returned from API) 14 | pub email_verified: bool, 15 | pub mfa_enabled: bool, 16 | pub totp_enabled: bool, 17 | pub webauthn_enabled: bool, 18 | } 19 | 20 | #[derive(Debug, Serialize, Deserialize)] 21 | pub struct Pat { 22 | /// The ID of the pat 23 | pub id: String, 24 | /// The name of the pat 25 | pub name: Option, 26 | /// The pat token 27 | pub pat: String, 28 | /// The date the pat was created 29 | pub created_at: String, 30 | } 31 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | // TODO: use Rust-PHF for this? 4 | /*pub const MULTIPLIERS: HashMap<&str, i64> = HashMap::from([ 5 | ("B", 1), 6 | ("KB", 1024), 7 | ("MB", 1024 * 1024), 8 | ("GB", 1024 * 1024 * 1024), 9 | ]);*/ 10 | 11 | pub fn get_bytes(ram: &str) -> i64 { 12 | let multipliers: HashMap<&str, i64> = HashMap::from([ 13 | // TODO: implement bytes (maybe check for "B" after checking hashmap?) 14 | // ("B", 1), 15 | ("KB", 1024), 16 | // TODO: is this broken? 17 | ("MB", 1024 * 1024), 18 | ("GB", 1024 * 1024 * 1024), 19 | ]); 20 | 21 | let ram = ram.to_uppercase(); 22 | 23 | let unit = multipliers 24 | .iter() 25 | .find(|(&k, _)| ram.ends_with(k)) 26 | .unwrap(); 27 | 28 | let multiplier = multipliers.get(unit.0).unwrap().to_owned(); 29 | let size = ram.replace(unit.0, "").parse::().unwrap(); 30 | 31 | return size * multiplier; 32 | } 33 | 34 | // TODO: make id parsers here 35 | --------------------------------------------------------------------------------