1use edda_auth::protocol::{AuthExport, CampaignSettings, CreatedCampaign, NewCampaign, SetupCode};
7use edda_types::{
8 Edition,
9 admin::{CharactersExport, CombatExport, Deleted, Imported, MonstersExport},
10};
11use serde::{Deserialize, Serialize, de::DeserializeOwned};
12use uuid::Uuid;
13
14use crate::{ClientError, send};
15
16#[derive(Debug, Clone)]
18pub struct AdminClient {
19 base_url: String,
20 http: reqwest::Client,
21}
22
23impl AdminClient {
24 pub fn new(base_url: impl Into<String>, token: &str) -> Self {
26 Self {
27 base_url: base_url.into().trim_end_matches('/').to_string(),
28 http: crate::http_client(Some(token)),
29 }
30 }
31
32 fn url(&self, path: &str) -> String {
33 format!("{}{path}", self.base_url)
34 }
35
36 pub async fn create_campaign(
39 &self,
40 name: &str,
41 edition: Edition,
42 ) -> Result<CreatedCampaign, ClientError> {
43 let new = NewCampaign {
44 name: name.to_owned(),
45 edition,
46 };
47 send(self.http.post(self.url("/admin/campaigns")).json(&new)).await
48 }
49
50 pub async fn setup_code(&self, campaign: Uuid) -> Result<String, ClientError> {
53 let url = self.url(&format!("/admin/campaigns/{campaign}/setup-code"));
54 let code: SetupCode = send(self.http.post(url)).await?;
55 Ok(code.code)
56 }
57
58 pub async fn get_settings(&self, campaign: Uuid) -> Result<CampaignSettings, ClientError> {
60 let url = self.url(&format!("/admin/campaigns/{campaign}/settings"));
61 send(self.http.get(url)).await
62 }
63
64 pub async fn settings(
68 &self,
69 campaign: Uuid,
70 settings: &CampaignSettings,
71 ) -> Result<CampaignSettings, ClientError> {
72 let url = self.url(&format!("/admin/campaigns/{campaign}/settings"));
73 send(self.http.put(url).json(settings)).await
74 }
75
76 pub async fn delete_campaign(&self, campaign: Uuid) -> Result<u64, ClientError> {
79 let url = self.url(&format!("/admin/campaigns/{campaign}"));
80 let deleted: Deleted = send(self.http.delete(url)).await?;
81 Ok(deleted.deleted)
82 }
83
84 pub async fn export<T: DeserializeOwned>(&self, campaign: Uuid) -> Result<T, ClientError> {
87 let url = self.url(&format!("/admin/campaigns/{campaign}/export"));
88 send(self.http.get(url)).await
89 }
90
91 pub async fn import<T: Serialize>(&self, campaign: Uuid, part: &T) -> Result<u64, ClientError> {
95 let url = self.url(&format!("/admin/campaigns/{campaign}/import"));
96 let imported: Imported =
97 send(self.http.put(url).json(part))
98 .await
99 .map_err(|e| match e {
100 ClientError::Exhausted(message) => ClientError::Conflict(message),
102 other => other,
103 })?;
104 Ok(imported.imported)
105 }
106}
107
108#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
111pub struct CampaignBundle {
112 pub format: u32,
114 pub auth: AuthExport,
115 pub characters: CharactersExport,
116 pub combat: CombatExport,
117 pub monsters: MonstersExport,
118}
119
120impl CampaignBundle {
121 pub const FORMAT: u32 = 1;
123
124 pub fn campaign(&self) -> Uuid {
126 self.auth.campaign.id
127 }
128}
129
130#[derive(Debug, Clone)]
132pub struct Services {
133 pub auth: AdminClient,
134 pub characters: AdminClient,
135 pub combat: AdminClient,
136 pub monsters: AdminClient,
137}
138
139impl Services {
140 pub async fn export_campaign(&self, campaign: Uuid) -> Result<CampaignBundle, ClientError> {
143 Ok(CampaignBundle {
144 format: CampaignBundle::FORMAT,
145 auth: self.auth.export(campaign).await?,
146 characters: self.characters.export(campaign).await?,
147 combat: self.combat.export(campaign).await?,
148 monsters: self.monsters.export(campaign).await?,
149 })
150 }
151
152 pub async fn import_campaign(&self, bundle: &CampaignBundle) -> Result<(), ClientError> {
156 if bundle.format != CampaignBundle::FORMAT {
157 return Err(ClientError::Rejected(format!(
158 "a campaign of format {}; this version reads {}",
159 bundle.format,
160 CampaignBundle::FORMAT
161 )));
162 }
163 let campaign = bundle.campaign();
164 self.auth.import(campaign, &bundle.auth).await?;
165 let rest = async {
166 self.characters.import(campaign, &bundle.characters).await?;
167 self.combat.import(campaign, &bundle.combat).await?;
168 self.monsters.import(campaign, &bundle.monsters).await
169 };
170 if let Err(e) = rest.await {
171 let _ = self.delete_campaign(campaign).await;
173 return Err(e);
174 }
175 Ok(())
176 }
177
178 pub async fn delete_campaign(&self, campaign: Uuid) -> Result<(), ClientError> {
181 self.characters.delete_campaign(campaign).await?;
182 self.combat.delete_campaign(campaign).await?;
183 self.monsters.delete_campaign(campaign).await?;
184 self.auth.delete_campaign(campaign).await?;
185 Ok(())
186 }
187}