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