1use mangadex_api_schema::v5::ChapterObject;
45use serde::Serialize;
46use url::Url;
47use uuid::Uuid;
48
49use crate::HttpClientRef;
50use mangadex_api_types::error::{Error, Result};
51use mangadex_api_types::{Language, MangaDexDateTime};
52
53#[derive(Debug, Serialize, Clone)]
54#[serde(rename_all = "camelCase")]
55pub struct CommitUploadSession<'a> {
56 #[serde(skip)]
58 pub(crate) http_client: HttpClientRef,
59
60 #[serde(skip)]
61 pub session_id: &'a Uuid,
62
63 chapter_draft: ChapterDraft<'a>,
64 pub page_order: Vec<Uuid>,
68}
69
70#[derive(Debug, Serialize, Clone)]
71#[serde(rename_all = "camelCase")]
72pub struct ChapterDraft<'a> {
73 pub volume: Option<&'a str>,
75 pub chapter: Option<&'a str>,
77 pub title: Option<&'a str>,
79 pub translated_language: Language,
80 pub external_url: Option<&'a Url>,
84 #[serde(skip_serializing_if = "Option::is_none")]
85 pub publish_at: Option<MangaDexDateTime>,
86}
87
88#[derive(Debug, Serialize, Clone, Default)]
90pub struct CommitUploadSessionBuilder<'a> {
91 #[serde(skip)]
92 pub(crate) http_client: HttpClientRef,
93
94 pub session_id: Option<&'a Uuid>,
95 pub page_order: Vec<Uuid>,
97
98 pub volume: Option<&'a str>,
100 pub chapter: Option<&'a str>,
102 pub title: Option<&'a str>,
104 pub translated_language: Option<Language>,
105 pub external_url: Option<&'a Url>,
109 pub publish_at: Option<MangaDexDateTime>,
110}
111
112impl<'a> CommitUploadSessionBuilder<'a> {
113 pub fn new(http_client: HttpClientRef) -> Self {
114 Self {
115 http_client,
116 ..Default::default()
117 }
118 }
119
120 pub fn session_id(mut self, session_id: &'a Uuid) -> Self {
122 self.session_id = Some(session_id);
123 self
124 }
125
126 pub fn page_order(mut self, page_order: Vec<Uuid>) -> Self {
128 self.page_order = page_order;
129 self
130 }
131
132 pub fn add_page(mut self, page: Uuid) -> Self {
134 self.page_order.push(page);
135 self
136 }
137
138 pub fn volume(mut self, volume: Option<&'a str>) -> Self {
142 self.volume = volume;
143 self
144 }
145
146 pub fn chapter(mut self, chapter: Option<&'a str>) -> Self {
150 self.chapter = chapter;
151 self
152 }
153
154 pub fn title(mut self, title: Option<&'a str>) -> Self {
158 self.title = title;
159 self
160 }
161
162 pub fn translated_language(mut self, translated_language: Language) -> Self {
166 self.translated_language = Some(translated_language);
167 self
168 }
169
170 pub fn external_url(mut self, external_url: Option<&'a Url>) -> Self {
176 self.external_url = external_url;
177 self
178 }
179
180 pub fn publish_at<DT: Into<MangaDexDateTime>>(mut self, publish_at: DT) -> Self {
182 self.publish_at = Some(publish_at.into());
183 self
184 }
185
186 fn validate(&self) -> std::result::Result<(), String> {
188 if self.session_id.is_none() {
189 return Err("session_id cannot be None".to_string());
190 }
191
192 if self.translated_language.is_none() {
193 return Err("translated_language cannot be None".to_string());
194 }
195
196 Ok(())
197 }
198
199 pub fn build(self) -> Result<CommitUploadSession<'a>> {
201 if let Err(error) = self.validate() {
202 return Err(Error::RequestBuilderError(error));
203 }
204
205 let session_id = self.session_id.unwrap();
206 let translated_language = self.translated_language.unwrap();
207
208 Ok(CommitUploadSession {
209 http_client: self.http_client,
210
211 session_id,
212 chapter_draft: ChapterDraft {
213 volume: self.volume,
214 chapter: self.chapter,
215 title: self.title,
216 translated_language,
217 external_url: self.external_url,
218 publish_at: self.publish_at,
219 },
220 page_order: self.page_order,
221 })
222 }
223}
224
225endpoint! {
226 PUT ("/upload/{}/commit", session_id),
227 #[body auth] CommitUploadSession<'_>,
228 ChapterObject
229}
230
231#[cfg(test)]
232mod tests {
233 use fake::faker::name::en::Name;
234 use fake::Fake;
235 use serde_json::json;
236 use time::OffsetDateTime;
237 use url::Url;
238 use uuid::Uuid;
239 use wiremock::matchers::{header, method, path_regex};
240 use wiremock::{Mock, MockServer, ResponseTemplate};
241
242 use crate::v5::AuthTokens;
243 use crate::{HttpClient, MangaDexClient};
244 use mangadex_api_types::{Language, MangaDexDateTime, RelationshipType};
245
246 #[tokio::test]
247 async fn commit_upload_session_fires_a_request_to_base_url() -> anyhow::Result<()> {
248 let mock_server = MockServer::start().await;
249 let http_client = HttpClient::builder()
250 .base_url(Url::parse(&mock_server.uri())?)
251 .auth_tokens(AuthTokens {
252 session: "sessiontoken".to_string(),
253 refresh: "refreshtoken".to_string(),
254 })
255 .build()?;
256 let mangadex_client = MangaDexClient::new_with_http_client(http_client);
257
258 let session_id = Uuid::new_v4();
259 let session_file_id = Uuid::new_v4();
260 let chapter_id = Uuid::new_v4();
261 let uploader_id = Uuid::new_v4();
262 let chapter_title: String = Name().fake();
263
264 let datetime = MangaDexDateTime::new(&OffsetDateTime::now_utc());
265
266 let _expected_body = json!({
267 "chapterDraft": {
268 "volume": "1",
269 "chapter": "2.5",
270 "title": chapter_title,
271 "translatedLanguage": "en",
272 "externalUrl": null
273 },
274 "pageOrder": [
275 session_file_id
276 ]
277 });
278 let response_body = json!({
279 "id": chapter_id,
280 "type": "chapter",
281 "attributes": {
282 "title": chapter_title,
283 "volume": "1",
284 "chapter": "2.5",
285 "pages": 4,
286 "translatedLanguage": "en",
287 "uploader": uploader_id,
288 "version": 1,
289 "createdAt": datetime.to_string(),
290 "updatedAt": datetime.to_string(),
291 "publishAt": datetime.to_string(),
292 "readableAt": datetime.to_string(),
293 },
294 "relationships": [],
295 });
296
297 Mock::given(method("PUT"))
298 .and(path_regex(r"/upload/[0-9a-fA-F-]+/commit"))
299 .and(header("Authorization", "Bearer sessiontoken"))
300 .and(header("Content-Type", "application/json"))
301 .respond_with(ResponseTemplate::new(200).set_body_json(response_body))
304 .expect(1)
305 .mount(&mock_server)
306 .await;
307
308 let res = mangadex_client
309 .upload()
310 .commit_session()
311 .session_id(&session_id)
312 .volume(Some("1"))
313 .chapter(Some("2.5"))
314 .title(Some(&chapter_title))
315 .translated_language(Language::English)
316 .page_order(vec![session_file_id])
317 .build()?
318 .send()
319 .await?;
320
321 assert_eq!(res.id, chapter_id);
322 assert_eq!(res.type_, RelationshipType::Chapter);
323 assert_eq!(res.attributes.title, chapter_title);
324 assert_eq!(res.attributes.volume, Some("1".to_string()));
325 assert_eq!(res.attributes.chapter, Some("2.5".to_string()));
326 assert_eq!(res.attributes.pages, 4);
327 assert_eq!(res.attributes.translated_language, Language::English);
328 assert_eq!(res.attributes.uploader, Some(uploader_id));
329 assert_eq!(res.attributes.external_url, None);
330 assert_eq!(res.attributes.version, 1);
331 assert_eq!(res.attributes.created_at.to_string(), datetime.to_string());
332 assert_eq!(
333 res.attributes.updated_at.as_ref().unwrap().to_string(),
334 datetime.to_string()
335 );
336 assert_eq!(res.attributes.publish_at.to_string(), datetime.to_string());
337 assert_eq!(res.attributes.readable_at.to_string(), datetime.to_string());
338
339 Ok(())
340 }
341}