mangadex_api/v5/user/follows/manga/feed/
get.rs

1//! Builder for the user manga feed endpoint to get a list of new chapters for a user's manga.
2//!
3//! <https://api.mangadex.org/swagger.html#/Feed/get-user-follows-manga-feed>
4//!
5//! # Examples
6//!
7//! ```rust
8//! use mangadex_api::v5::MangaDexClient;
9//! // use mangadex_api_types::{Password, Username};
10//!
11//! # async fn run() -> anyhow::Result<()> {
12//! let client = MangaDexClient::default();
13//!
14//! /*
15//!
16//!     let _login_res = client
17//!         .auth()
18//!         .login()
19//!         .post()
20//!         .username(Username::parse("myusername")?)
21//!         .password(Password::parse("hunter23")?)
22//!         .send()
23//!         .await?;
24//!
25//!  */
26//!
27//! let res = client
28//!     .user()
29//!     .follows()
30//!     .manga()
31//!     .feed()
32//!     .get()
33//!     .limit(1u32)
34//!     .send()
35//!     .await?;
36//!
37//! println!("Manga feed: {:?}", res);
38//! # Ok(())
39//! # }
40//! ```
41
42use derive_builder::Builder;
43use serde::Serialize;
44use uuid::Uuid;
45
46use crate::HttpClientRef;
47use mangadex_api_schema::v5::ChapterListResponse;
48use mangadex_api_types::{
49    ContentRating, IncludeExternalUrl, IncludeFuturePages, IncludeFuturePublishAt,
50    IncludeFutureUpdates, Language, MangaDexDateTime, MangaFeedSortOrder,
51    ReferenceExpansionResource,
52};
53
54#[cfg_attr(
55    feature = "deserializable-endpoint",
56    derive(serde::Deserialize, getset::Getters, getset::Setters)
57)]
58#[derive(Debug, Serialize, Clone, Builder, Default)]
59#[serde(rename_all = "camelCase")]
60#[builder(
61    setter(into, strip_option),
62    default,
63    build_fn(error = "mangadex_api_types::error::BuilderError")
64)]
65#[cfg_attr(feature = "non_exhaustive", non_exhaustive)]
66#[cfg_attr(
67    feature = "custom_list_v2",
68    deprecated(
69        since = "3.0.0-rc.1",
70        note = "After the introduction of the Subscription system, this endpoint will be removed in v3"
71    )
72)]
73pub struct GetFollowedMangaFeed {
74    /// This should never be set manually as this is only for internal use.
75    #[doc(hidden)]
76    #[serde(skip)]
77    #[builder(pattern = "immutable")]
78    #[cfg_attr(feature = "deserializable-endpoint", getset(set = "pub", get = "pub"))]
79    pub http_client: HttpClientRef,
80
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub limit: Option<u32>,
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub offset: Option<u32>,
85    #[builder(setter(each = "add_translated_language"))]
86    #[serde(skip_serializing_if = "Vec::is_empty")]
87    pub translated_language: Vec<Language>,
88    #[builder(setter(each = "add_original_language"))]
89    #[serde(skip_serializing_if = "Vec::is_empty")]
90    pub original_language: Vec<Language>,
91    #[builder(setter(each = "exclude_original_language"))]
92    #[serde(skip_serializing_if = "Vec::is_empty")]
93    pub excluded_original_language: Vec<Language>,
94    #[builder(setter(each = "add_content_rating"))]
95    #[serde(skip_serializing_if = "Vec::is_empty")]
96    pub content_rating: Vec<ContentRating>,
97    /// Groups to exclude from the results.
98    #[builder(setter(each = "excluded_group"))]
99    #[serde(skip_serializing_if = "Vec::is_empty")]
100    pub excluded_groups: Vec<Uuid>,
101    /// Uploaders to exclude from the results.
102    #[builder(setter(each = "excluded_uploader"))]
103    #[serde(skip_serializing_if = "Vec::is_empty")]
104    pub excluded_uploaders: Vec<Uuid>,
105    /// Flag to include future chapter updates in the results.
106    ///
107    /// Default: `IncludeFutureUpdates::Include` (1)
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub include_future_updates: Option<IncludeFutureUpdates>,
110    /// DateTime string with following format: `YYYY-MM-DDTHH:MM:SS`.
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub created_at_since: Option<MangaDexDateTime>,
113    /// DateTime string with following format: `YYYY-MM-DDTHH:MM:SS`.
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub updated_at_since: Option<MangaDexDateTime>,
116    /// DateTime string with following format: `YYYY-MM-DDTHH:MM:SS`.
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub publish_at_since: Option<MangaDexDateTime>,
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub order: Option<MangaFeedSortOrder>,
121    #[builder(setter(each = "include"))]
122    #[serde(skip_serializing_if = "Vec::is_empty")]
123    pub includes: Vec<ReferenceExpansionResource>,
124    #[builder(default)]
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub include_empty_pages: Option<IncludeFuturePages>,
127    #[builder(default)]
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub include_future_publish_at: Option<IncludeFuturePublishAt>,
130    #[builder(default)]
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub include_external_url: Option<IncludeExternalUrl>,
133}
134
135endpoint! {
136    GET "/user/follows/manga/feed",
137    #[query auth] GetFollowedMangaFeed,
138    #[flatten_result] ChapterListResponse,
139    GetFollowedMangaFeedBuilder
140}
141
142#[cfg(test)]
143mod tests {
144    use fake::faker::name::en::Name;
145    use fake::Fake;
146    use serde_json::json;
147    use time::OffsetDateTime;
148    use url::Url;
149    use uuid::Uuid;
150    use wiremock::matchers::{header, method, path_regex};
151    use wiremock::{Mock, MockServer, ResponseTemplate};
152
153    use crate::v5::AuthTokens;
154    use crate::{HttpClient, MangaDexClient};
155    use mangadex_api_types::MangaDexDateTime;
156
157    #[tokio::test]
158    async fn get_followed_manga_feed_fires_a_request_to_base_url() -> anyhow::Result<()> {
159        let mock_server = MockServer::start().await;
160        let http_client: HttpClient = HttpClient::builder()
161            .base_url(Url::parse(&mock_server.uri())?)
162            .auth_tokens(AuthTokens {
163                session: "sessiontoken".to_string(),
164                refresh: "refreshtoken".to_string(),
165            })
166            .build()?;
167        let mangadex_client = MangaDexClient::new_with_http_client(http_client);
168
169        let chapter_id = Uuid::new_v4();
170        let uploader_id = Uuid::new_v4();
171        let chapter_title: String = Name().fake();
172
173        let datetime = MangaDexDateTime::new(&OffsetDateTime::now_utc());
174
175        let response_body = json!({
176            "result": "ok",
177            "response": "collection",
178            "data": [
179                {
180                    "id": chapter_id,
181                    "type": "chapter",
182                    "attributes": {
183                        "title": chapter_title,
184                        "volume": "1",
185                        "chapter": "1.5",
186                        "pages": 4,
187                        "translatedLanguage": "en",
188                        "uploader": uploader_id,
189                        "version": 1,
190                        "createdAt": datetime.to_string(),
191                        "updatedAt": datetime.to_string(),
192                        "publishAt": datetime.to_string(),
193                        "readableAt": datetime.to_string(),
194                    },
195                    "relationships": [],
196                },
197            ],
198            "limit": 1,
199            "offset": 0,
200            "total": 1
201        });
202
203        Mock::given(method("GET"))
204            .and(path_regex(r"/user/follows/manga/feed"))
205            .and(header("Authorization", "Bearer sessiontoken"))
206            .respond_with(ResponseTemplate::new(200).set_body_json(response_body))
207            .expect(1)
208            .mount(&mock_server)
209            .await;
210
211        let _ = mangadex_client
212            .user()
213            .follows()
214            .manga()
215            .feed()
216            .get()
217            .limit(1u32)
218            .send()
219            .await?;
220
221        Ok(())
222    }
223}