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