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}