1use crate::{http_client, models::Entry};
2use serde::{de::DeserializeOwned, Serialize};
3use serde_json::json;
4use serde_json::Value;
5
6pub struct ContentfulManagementClient {
7 management_api_access_token: String,
8 space_id: String,
9 base_url: String,
10 environment_id: String,
11}
12
13impl ContentfulManagementClient {
14 pub fn new(management_api_access_token: &str, space_id: &str) -> ContentfulManagementClient {
15 let environment_id = "master".into();
16 ContentfulManagementClient {
17 base_url: "https://api.contentful.com/spaces".into(),
18 management_api_access_token: management_api_access_token.into(),
19 space_id: space_id.into(),
20 environment_id,
21 }
22 }
23
24 pub fn with_environment<S>(
25 management_api_access_token: &str,
26 space_id: &str,
27 environment_id: &str,
28 ) -> ContentfulManagementClient {
29 ContentfulManagementClient {
30 base_url: "https://api.contentful.com/spaces".into(),
31 management_api_access_token: management_api_access_token.into(),
32 space_id: space_id.into(),
33 environment_id: environment_id.into(),
34 }
35 }
36
37 fn get_entry_url(&self, entry_id: &str) -> String {
38 let url = format!(
39 "{base_url}/{space_id}/environments/{environment_id}/entries/{entry_id}",
40 base_url = &self.base_url,
41 space_id = &self.space_id,
42 environment_id = &self.environment_id,
43 entry_id = &entry_id
44 );
45 url
46 }
47 fn get_entries_url(&self) -> String {
48 let url = format!(
49 "{base_url}/{space_id}/environments/{environment_id}/entries",
50 base_url = &self.base_url,
51 space_id = &self.space_id,
52 environment_id = &self.environment_id,
53 );
54 url
55 }
56
57 pub async fn get_entry(
58 &self,
59 entry_id: &str,
60 ) -> Result<Option<Entry<Value>>, Box<dyn std::error::Error>> {
61 let url = self.get_entry_url(entry_id);
62 let entry =
63 http_client::get::<Entry<Value>>(&url, &self.management_api_access_token).await?;
64 Ok(entry)
65 }
66
67 pub async fn get_entry_for_locale<T>(
68 &self,
69 entry_id: &str,
70 locale: &str,
71 ) -> Result<Option<Entry<T>>, Box<dyn std::error::Error>>
72 where
73 T: DeserializeOwned + Serialize,
74 {
75 let url = self.get_entry_url(entry_id);
76 if let Some(entry_json) =
77 http_client::get::<Entry<Value>>(&url, &self.management_api_access_token).await?
78 {
79 let entry_typed =
80 helpers::convert_json_object_to_typed_entry(entry_json.fields.clone(), locale)?;
81 let entry = Entry::new(entry_typed, entry_json.sys.clone());
82 Ok(Some(entry))
83 } else {
84 Ok(None)
85 }
86 }
87
88 pub async fn create_entry_from_json<T>(
89 &self,
90 entry: &Value,
91 content_type_id: &str,
92 ) -> Result<T, Box<dyn std::error::Error>>
93 where
94 T: DeserializeOwned,
95 {
96 let url = self.get_entries_url();
97 let mut json = http_client::post(
98 &url,
99 &self.management_api_access_token,
100 content_type_id,
101 entry,
102 )
103 .await?;
104 let entry_created_fields = json.get_mut("fields").unwrap();
105 let entry_created_string = entry_created_fields.to_string();
106 let entry_created = serde_json::from_str::<T>(&entry_created_string)?;
107
108 Ok(entry_created)
109 }
110
111 pub async fn create_entry<T>(
112 &self,
113 entry: &T,
114 content_type_id: &str,
115 ) -> Result<T, Box<dyn std::error::Error>>
116 where
117 T: DeserializeOwned + Serialize,
118 {
119 let entry_json = json!({ "fields": entry });
120 self.create_entry_from_json::<T>(&entry_json, content_type_id)
121 .await
122 }
123
124 pub async fn create_entry_for_locale<T>(
125 &self,
126 entry: &T,
127 content_type_id: &str,
128 locale: &str,
129 ) -> Result<T, Box<dyn std::error::Error>>
130 where
131 T: DeserializeOwned + Serialize,
132 {
133 let entry_to_create = helpers::reconstruct_json_object_with_locale(entry, locale)?;
134 let updated_entry_json = self
135 .create_entry::<Value>(&entry_to_create, content_type_id)
136 .await?;
137 let updated_entry =
138 helpers::convert_json_object_to_typed_entry(updated_entry_json, locale)?;
139 Ok(updated_entry)
140 }
141
142 pub async fn create_or_update_entry_from_json(
143 &self,
144 entry: &Value,
145 entry_id: &str,
146 version: &Option<i32>,
147 content_type_id: &str,
148 ) -> Result<Value, Box<dyn std::error::Error>> {
149 let url = self.get_entry_url(entry_id);
150 let json = http_client::put(
151 &url,
152 &self.management_api_access_token,
153 version,
154 content_type_id,
155 entry,
156 )
157 .await?;
158 Ok(json)
159 }
160
161 pub async fn create_or_update_entry(
162 &self,
163 entry: &Entry<Value>,
164 id: &str,
165 content_type_id: &str,
166 ) -> Result<Entry<Value>, Box<dyn std::error::Error>> {
167 let entry_updated = self
168 .create_or_update_entry_from_json(
169 &json!(entry),
170 id,
171 &entry.sys.version,
172 content_type_id,
173 )
174 .await?;
175 let entry_updated_string = entry_updated.to_string();
176 let entry = serde_json::from_str::<Entry<Value>>(&entry_updated_string)?;
177 Ok(entry)
178 }
179
180 pub async fn create_or_update_entry_for_locale<T>(
181 &self,
182 entry: &Entry<T>,
183 id: &str,
184 locale: &str,
185 content_type_id: &str,
186 ) -> Result<Entry<T>, Box<dyn std::error::Error>>
187 where
188 T: DeserializeOwned + Serialize,
189 {
190 let entry_json = helpers::reconstruct_json_object_with_locale(&entry.fields, locale)?;
191 let entry_to_update = Entry::new(entry_json, entry.sys.clone());
192 let updated_entry_json = self
193 .create_or_update_entry(&entry_to_update, id, content_type_id)
194 .await?;
195 let updated_entry_typed =
196 helpers::convert_json_object_to_typed_entry(json!(updated_entry_json.fields), locale)?;
197 let updated_entry = Entry::new(updated_entry_typed, updated_entry_json.sys);
198 Ok(updated_entry)
199 }
200}
201
202mod helpers {
203 use serde::{de::DeserializeOwned, Serialize};
204 use serde_json::{json, Value};
205
206 pub fn reconstruct_json_object_with_locale<T>(
207 entry: &T,
208 locale: &str,
209 ) -> Result<Value, Box<dyn std::error::Error>>
210 where
211 T: Serialize,
212 {
213 let mut entry_json = json!(entry);
214 let mut fields_map = serde_json::Map::new();
215
216 if entry_json.is_object() {
217 let entry_object = entry_json.as_object_mut().unwrap();
218 for (field_name, field_value) in entry_object {
219 fields_map.insert(field_name.into(), json!({ locale: field_value }));
225 }
227 } else {
228 unimplemented!();
229 }
230
231 Ok(json!(fields_map))
232 }
233
234 pub fn convert_json_object_to_typed_entry<T>(
235 entry_json: Value,
236 locale: &str,
237 ) -> Result<T, Box<dyn std::error::Error>>
238 where
239 T: DeserializeOwned,
240 {
241 let mut entry_created_map = serde_json::Map::new();
242
243 if entry_json.is_object() {
244 let entry_object = entry_json.as_object().unwrap();
245 for (field_name, field_value) in entry_object {
246 if field_value.is_object() {
247 entry_created_map
248 .insert(field_name.into(), field_value.get(locale).unwrap().clone());
249 } else if field_value.is_array() {
250 todo!();
251 } else {
252 todo!();
253 }
254 }
255 } else {
256 todo!();
257 }
258
259 let entry_string = json!(entry_created_map).to_string();
260 let created_entry = serde_json::from_str::<T>(&entry_string)?;
261 Ok(created_entry)
262 }
263}