nimbuspulse_client/
lib.rs

1use anyhow::{bail, Ok, Result};
2use serde::Serialize;
3pub use types::instance::{Instance, InstanceStatus, Terrain};
4pub use types::region::Region;
5use types::{dcs_runtime::DcsRuntime, system_resources::ServerResources};
6use uuid::Uuid;
7
8mod types;
9
10#[derive(Debug, Serialize)]
11#[cfg_attr(test, derive(ts_rs::TS))]
12#[cfg_attr(test, ts(export, export_to = "../../javascript/lib/types/"))]
13pub struct CreateInstanceRequest {
14    pub product_id: Uuid,
15    pub region: Region,
16    pub settings: DcsSettingsPayload,
17    pub active_mods: Vec<String>,
18    pub wanted_terrains: Vec<Terrain>,
19}
20
21#[derive(Debug, Serialize)]
22#[cfg_attr(test, derive(ts_rs::TS))]
23#[cfg_attr(test, ts(export, export_to = "../../javascript/lib/types/"))]
24pub struct DcsSettingsPayload {
25    pub initial_server_name: String,
26    pub initial_server_password: String,
27    pub initial_max_players: u32,
28    pub enable_io: bool,
29    pub enable_os: bool,
30    pub enable_lfs: bool,
31    pub initial_use_voice_chat: bool,
32}
33
34#[derive(Debug, Clone)]
35pub struct Client {
36    api_key: String,
37    reqwest_client: reqwest::Client,
38}
39
40impl Client {
41    const BASE_URL: &'static str = "https://coordinator.nimbuspulse.com";
42
43    pub fn new(api_key: impl Into<String>) -> Self {
44        Self {
45            reqwest_client: reqwest::Client::new(),
46            api_key: api_key.into(),
47        }
48    }
49
50    pub fn set_api_key(&mut self, api_key: impl Into<String>) {
51        self.api_key = api_key.into();
52    }
53
54    pub async fn create_server(
55        &self,
56        region: Region,
57        name: impl Into<String>,
58        password: Option<impl Into<String>>,
59        max_players: u32,
60        plan: Uuid,
61        active_mods: Vec<impl Into<String>>,
62        terrains: Vec<Terrain>,
63        use_voice_chat: bool,
64        enable_io: bool,
65        enable_os: bool,
66        enable_lfs: bool,
67    ) -> Result<Instance> {
68        let payload = CreateInstanceRequest {
69            product_id: plan,
70            region,
71            settings: DcsSettingsPayload {
72                initial_server_name: name.into(),
73                initial_server_password: password.map(|p| p.into()).unwrap_or_default(),
74                initial_max_players: max_players,
75                initial_use_voice_chat: use_voice_chat,
76                enable_io,
77                enable_os,
78                enable_lfs,
79            },
80            active_mods: active_mods.into_iter().map(|m| m.into()).collect(),
81            wanted_terrains: terrains,
82        };
83
84        let response = self
85            .reqwest_client
86            .post(format!("{}/game_servers", Self::BASE_URL))
87            .bearer_auth(self.api_key.clone())
88            .json(&payload)
89            .send()
90            .await?;
91
92        if !response.status().is_success() {
93            bail!(format!(
94                "Failed to create server: {:?}",
95                response.text().await?
96            ));
97        }
98
99        Ok(response.json::<Instance>().await?)
100    }
101
102    pub async fn get_runtime(&self, id: &Uuid) -> Result<DcsRuntime> {
103        let response = self
104            .reqwest_client
105            .get(format!("{}/game_servers/{}/runtime", Self::BASE_URL, id))
106            .bearer_auth(self.api_key.clone())
107            .send()
108            .await?;
109
110        if !response.status().is_success() {
111            bail!(format!("Failed to get runtime: {:?}", response));
112        }
113
114        Ok(response.json::<DcsRuntime>().await?)
115    }
116
117    pub async fn get_server_resources(&self, id: &Uuid) -> Result<ServerResources> {
118        let response = self
119            .reqwest_client
120            .get(format!("{}/game_servers/{}/resources", Self::BASE_URL, id))
121            .bearer_auth(self.api_key.clone())
122            .send()
123            .await?;
124
125        if !response.status().is_success() {
126            bail!(format!("Failed to get server resources: {:?}", response));
127        }
128
129        Ok(response.json::<ServerResources>().await?)
130    }
131
132    pub async fn add_missions(&self, id: &Uuid, missions: Vec<String>) -> Result<()> {
133        let response = self
134            .reqwest_client
135            .post(format!(
136                "{}/game_servers/{}/dcs-api/missions",
137                Self::BASE_URL,
138                id
139            ))
140            .bearer_auth(self.api_key.clone())
141            .json(&missions)
142            .send()
143            .await?;
144
145        if !response.status().is_success() {
146            bail!("Failed to add missions: {:?}", response.text().await?);
147        }
148
149        Ok(())
150    }
151
152    pub async fn start_mission(&self, id: &Uuid, mission_idx: u32) -> Result<()> {
153        let response = self
154            .reqwest_client
155            .post(format!(
156                "{}/game_servers/{}/dcs-api/missions/{}/start",
157                Self::BASE_URL,
158                id,
159                mission_idx
160            ))
161            .bearer_auth(self.api_key.clone())
162            .send()
163            .await?;
164
165        if !response.status().is_success() {
166            bail!("Failed to start mission: {:?}", response);
167        }
168
169        Ok(())
170    }
171
172    pub async fn get_servers(&self) -> Result<Vec<Instance>> {
173        let response = self
174            .reqwest_client
175            .get(format!("{}/game_servers", Self::BASE_URL))
176            .bearer_auth(self.api_key.clone())
177            .send()
178            .await?;
179
180        if !response.status().is_success() {
181            bail!(format!("Failed to create server: {:?}", response));
182        }
183
184        Ok(response.json::<Vec<Instance>>().await?)
185    }
186
187    pub async fn start_server(&self, id: &Uuid) -> Result<()> {
188        let response = self
189            .reqwest_client
190            .post(format!("{}/game_servers/{}/start", Self::BASE_URL, id))
191            .bearer_auth(self.api_key.clone())
192            .send()
193            .await?;
194
195        if !response.status().is_success() {
196            bail!(format!("Failed to start server: {:?}", response));
197        }
198
199        Ok(())
200    }
201
202    pub async fn stop_server(&self, id: &Uuid) -> Result<()> {
203        let response = self
204            .reqwest_client
205            .post(format!("{}/game_servers/{}/stop", Self::BASE_URL, id))
206            .bearer_auth(self.api_key.clone())
207            .send()
208            .await?;
209
210        if !response.status().is_success() {
211            bail!(format!("Failed to stop server: {:?}", response));
212        }
213
214        Ok(())
215    }
216
217    pub async fn resume_server(&self, id: &Uuid) -> Result<()> {
218        let response = self
219            .reqwest_client
220            .post(format!(
221                "{}/game_servers/{}/dcs-api/resume",
222                Self::BASE_URL,
223                id
224            ))
225            .bearer_auth(self.api_key.clone())
226            .send()
227            .await?;
228
229        if !response.status().is_success() {
230            bail!("Failed to resume server: {:?}", response);
231        }
232
233        Ok(())
234    }
235
236    pub async fn pause_server(&self, id: &Uuid) -> Result<()> {
237        let response = self
238            .reqwest_client
239            .post(format!(
240                "{}/game_servers/{}/dcs-api/pause",
241                Self::BASE_URL,
242                id
243            ))
244            .bearer_auth(self.api_key.clone())
245            .send()
246            .await?;
247
248        if !response.status().is_success() {
249            bail!("Failed to pause server: {:?}", response);
250        }
251
252        Ok(())
253    }
254
255    pub async fn delete_server(&self, id: &Uuid) -> Result<()> {
256        let response = self
257            .reqwest_client
258            .delete(format!("{}/game_servers/{}", Self::BASE_URL, id))
259            .bearer_auth(self.api_key.clone())
260            .send()
261            .await?;
262
263        if !response.status().is_success() {
264            bail!(format!("Failed to delete server: {:?}", response));
265        }
266
267        Ok(())
268    }
269}