mangadex_api/v5/manga/id/read/
post.rs

1//! Mark multiple chapters for one manga as read and/or unread.
2//!
3//! <https://api.mangadex.org/docs/swagger.html#/Chapter/post-manga-chapter-readmarkers>
4//!
5//! # Examples
6//!
7//! ```rust
8//! use uuid::Uuid;
9//!
10//! use mangadex_api::MangaDexClient;
11//! // use mangadex_api_types::{Password, Username};
12//!
13//! # async fn run() -> anyhow::Result<()> {
14//! let client = MangaDexClient::default();
15//!
16//! /*
17//!     // Put your login script here
18//!     let _login_res = client
19//!         .auth()
20//!         .login()
21//!         .username(Username::parse("myusername")?)
22//!         .password(Password::parse("hunter23")?)
23//!         .build()?
24//!         .send()
25//!         .await?;
26//!
27//!  */
28//!
29//! let manga_id = Uuid::new_v4();
30//! let read_chapter_id = Uuid::new_v4();
31//! let unread_chapter_id = Uuid::new_v4();
32//! let res = client
33//!     .manga()
34//!     .id(manga_id)
35//!     .read()
36//!     .post()
37//!     .mark_chapter_read(read_chapter_id)
38//!     .mark_chapter_unread(unread_chapter_id)
39//!     .send()
40//!     .await?;
41//!
42//! println!("response: {:?}", res);
43//! # Ok(())
44//! # }
45//! ```
46
47use derive_builder::Builder;
48use serde::Serialize;
49use uuid::Uuid;
50
51use crate::HttpClientRef;
52use mangadex_api_schema::NoData;
53use mangadex_api_types::error::Result;
54
55/// Mark multiple manga chapters as read and/or unread for the current user.
56///
57/// Makes a request to `POST /manga/{id}/read`.
58#[cfg_attr(
59    feature = "deserializable-endpoint",
60    derive(serde::Deserialize, getset::Getters, getset::Setters)
61)]
62#[derive(Debug, Serialize, Clone, Builder, Default)]
63#[serde(rename_all = "camelCase")]
64#[builder(
65    setter(into, strip_option),
66    build_fn(error = "mangadex_api_types::error::BuilderError")
67)]
68pub struct MarkChapterBatch {
69    /// This should never be set manually as this is only for internal use.
70    #[doc(hidden)]
71    #[serde(skip)]
72    #[builder(pattern = "immutable")]
73    #[cfg_attr(feature = "deserializable-endpoint", getset(set = "pub", get = "pub"))]
74    pub http_client: HttpClientRef,
75
76    #[serde(skip_serializing)]
77    pub manga_id: Uuid,
78    #[builder(setter(each = "mark_chapter_read"), default)]
79    pub chapter_ids_read: Vec<Uuid>,
80    #[builder(setter(each = "mark_chapter_unread"), default)]
81    pub chapter_ids_unread: Vec<Uuid>,
82    #[serde(skip_serializing)]
83    #[builder(default)]
84    pub update_history: bool,
85}
86
87endpoint! {
88    POST ("/manga/{}/read?updateHistory={}", manga_id, update_history),
89    #[body auth] MarkChapterBatch,
90    #[discard_result] Result<NoData>,
91    MarkChapterBatchBuilder
92}
93
94#[cfg(test)]
95mod tests {
96    use serde_json::json;
97    use url::Url;
98    use uuid::Uuid;
99    use wiremock::matchers::{body_json, header, method, path_regex};
100    use wiremock::{Mock, MockServer, ResponseTemplate};
101
102    use crate::v5::AuthTokens;
103    use crate::{HttpClient, MangaDexClient};
104
105    #[tokio::test]
106    async fn mark_manga_chapters_read_fires_a_request_to_base_url() -> anyhow::Result<()> {
107        let mock_server = MockServer::start().await;
108        let http_client: HttpClient = HttpClient::builder()
109            .base_url(Url::parse(&mock_server.uri())?)
110            .auth_tokens(AuthTokens {
111                session: "sessiontoken".to_string(),
112                refresh: "refreshtoken".to_string(),
113            })
114            .build()?;
115        let mangadex_client = MangaDexClient::new_with_http_client(http_client);
116
117        let manga_id = Uuid::new_v4();
118        let read_chapter_id = Uuid::new_v4();
119        let unread_chapter_id = Uuid::new_v4();
120        let expected_body = json!({
121            "chapterIdsRead": [read_chapter_id],
122            "chapterIdsUnread": [unread_chapter_id]
123        });
124        let response_body = json!({"result": "ok"});
125
126        Mock::given(method("POST"))
127            .and(path_regex(r"/manga/[0-9a-fA-F-]+/read"))
128            .and(header("Authorization", "Bearer sessiontoken"))
129            .and(body_json(expected_body))
130            .respond_with(ResponseTemplate::new(200).set_body_json(response_body))
131            .expect(1)
132            .mount(&mock_server)
133            .await;
134
135        mangadex_client
136            .manga()
137            .id(manga_id)
138            .read()
139            .post()
140            .mark_chapter_read(read_chapter_id)
141            .mark_chapter_unread(unread_chapter_id)
142            .send()
143            .await?;
144
145        Ok(())
146    }
147}