rust_freely/client/
models.rs

1/// This module provides API model definitions & associated methods.
2pub mod api_models {
3    
4
5    /// This module provides models related to [User]
6    pub mod users {
7        use chrono::{DateTime, Utc};
8        use serde_derive::{Deserialize, Serialize};
9
10        #[derive(Clone, Debug, Serialize, Deserialize)]
11        /// Base User model
12        pub struct User {
13            /// Username
14            pub username: String,
15
16            /// Email (may not be present based on instance settings & associated request)
17            pub email: Option<String>,
18
19            /// Creation D/T (may not be present based on instance settings & associated request)
20            pub created: Option<DateTime<Utc>>,
21        }
22    }
23
24    /// This module provides models related to [Post]
25    pub mod posts {
26        use chrono::{DateTime, Utc};
27        use derive_builder::Builder;
28        use reqwest::Method;
29        use serde_derive::{Deserialize, Serialize};
30
31        use crate::api_client::{ApiError, Client};
32
33        use super::collections::{Collection, MovePost, MoveResult};
34
35        #[derive(Clone, Debug, Serialize, Deserialize)]
36        /// Enum describing the appearance/font of a post
37        pub enum PostAppearance {
38            #[serde(rename = "sans")]
39            ///
40            SansSerif,
41
42            #[serde(rename = "serif")]
43            #[serde(alias = "norm")]
44            #[serde(alias = "serif")]
45            ///
46            Serif,
47
48            #[serde(rename = "wrap")]
49            ///
50            Wrap,
51
52            #[serde(rename = "mono")]
53            ///
54            Mono,
55
56            #[serde(rename = "code")]
57            ///
58            Code,
59        }
60
61        #[derive(Clone, Debug, Serialize, Deserialize, Builder)]
62        /// Struct describing a pending update to a [Post]
63        pub struct PostUpdate {
64            #[serde(skip_serializing)]
65            /// [Client] instance
66            pub client: Option<Client>,
67
68            #[serde(skip_serializing)]
69            /// Post ID
70            pub id: String,
71
72            /// Post token, if not owned
73            pub token: Option<String>,
74
75            /// New post body
76            pub body: String,
77
78            /// New post title
79            pub title: Option<String>,
80
81            /// New post font
82            pub font: Option<PostAppearance>,
83
84            /// New post language
85            pub lang: Option<String>,
86
87            /// New post RTL
88            pub rtl: bool,
89        }
90
91        impl PostUpdate {
92            /// Dispatches an update request to the server.
93            pub async fn update(&self) -> Result<Post, ApiError> {
94                if let Some(client) = self.client.clone() {
95                    client
96                        .api()
97                        .post::<Post, PostUpdate>(
98                            format!("/posts/{}", self.id).as_str(),
99                            Some(self.clone()),
100                        )
101                        .await
102                        .and_then(|mut p| Ok(p.with_client(client.clone())))
103                } else {
104                    Err(ApiError::UsageError {})
105                }
106            }
107        }
108
109        #[derive(Clone, Debug, Serialize, Deserialize)]
110        /// Main struct describing a single Post
111        pub struct Post {
112            ///
113            pub client: Option<Client>,
114            ///
115            pub id: String,
116            ///
117            pub slug: Option<String>,
118            ///
119            pub appearance: Option<PostAppearance>,
120            ///
121            pub language: Option<String>,
122            ///
123            pub rtl: bool,
124            ///
125            pub created: Option<DateTime<Utc>>,
126            ///
127            pub title: Option<String>,
128            ///
129            pub body: String,
130            ///
131            pub tags: Vec<String>,
132            ///
133            pub views: Option<u64>,
134            ///
135            pub collection: Option<Collection>,
136            ///
137            pub token: Option<String>,
138        }
139
140        impl Post {
141            #[doc(hidden)]
142            pub fn with_client(&mut self, client: Client) -> Self {
143                self.client = Some(client);
144                self.clone()
145            }
146
147            /// Returns a [PostUpdateBuilder] initialized with a [Client], the correct ID, and the specified body text
148            pub fn build_update(&self, body: String) -> PostUpdateBuilder {
149                PostUpdateBuilder::default()
150                    .client(self.client.clone())
151                    .id(self.id.clone())
152                    .body(body)
153                    .clone()
154            }
155
156            /// Dispatches an update with an existing [PostUpdate]
157            pub async fn update(&self, update: PostUpdate) -> Result<Post, ApiError> {
158                if let Some(client) = self.client.clone() {
159                    client
160                        .api()
161                        .post::<Post, PostUpdate>(
162                            format!("/posts/{}", self.id).as_str(),
163                            Some(update.clone()),
164                        )
165                        .await
166                        .and_then(|mut p| Ok(p.with_client(client.clone())))
167                } else {
168                    Err(ApiError::UsageError {})
169                }
170            }
171
172            /// Deletes this post
173            pub async fn delete(&self) -> Result<(), ApiError> {
174                if let Some(client) = self.client.clone() {
175                    let mut request = client
176                        .api()
177                        .request(format!("/posts/{}", self.id).as_str(), Method::DELETE)
178                        .unwrap();
179                    if !client.is_authenticated() && self.token.is_some() {
180                        request = request.query(&[("token", self.token.clone().unwrap())]);
181                    }
182                    if let Ok(result) = request.send().await {
183                        client.api().extract_response(result).await
184                    } else {
185                        Err(ApiError::ConnectionError {})
186                    }
187                } else {
188                    Err(ApiError::UsageError {})
189                }
190            }
191
192            /// Moves the post to a [Collection] by its alias
193            pub async fn move_to(&self, collection: &str) -> Result<MoveResult, ApiError> {
194                if let Some(client) = self.client.clone() {
195                    match client.collections().get(collection).await {
196                        Ok(coll) => {
197                            match client.is_authenticated() {
198                                true => coll.take_posts(&[MovePost::new(&self.id)]).await,
199                                false => coll.take_posts(&[MovePost {id: self.id.clone(), token: self.token.clone()}]).await
200                            }.and_then(|v| {
201                                match v.get(0) {
202                                    Some(item) => match item {
203                                        Ok(result) => Ok(result.clone()),
204                                        Err(result) => Ok(result.clone())
205                                    },
206                                    None => Err(ApiError::UnknownError {  })
207                                }
208                            })
209                        },
210                        Err(e) => Err(e) 
211                    }
212                } else {
213                    Err(ApiError::UsageError {})
214                }
215            }
216        }
217
218        #[derive(Clone, Debug, Serialize, Deserialize, Builder)]
219        /// Post creation struct
220        pub struct PostCreation {
221            #[serde(skip_serializing)]
222            /// [Client] instance
223            pub client: Option<Client>,
224
225            #[serde(skip_serializing)]
226            /// Collection to post to, if desired
227            pub collection: Option<String>,
228
229            /// Post body
230            pub body: String,
231
232            /// Post title
233            pub title: Option<String>,
234
235            /// Post font
236            pub font: Option<PostAppearance>,
237
238            /// Post language
239            pub lang: Option<String>,
240
241            /// Post RTL
242            pub rtl: Option<bool>,
243
244            /// Specific post creation DT
245            pub created: Option<DateTime<Utc>>,
246        }
247
248        impl PostCreation {
249            /// Publishes the described post to the server
250            pub async fn publish(&self) -> Result<Post, ApiError> {
251                if let Some(client) = self.client.clone() {
252                    if let Some(collection) = self.collection.clone() {
253                        client
254                            .api()
255                            .post::<Post, PostCreation>(
256                                format!("/collections/{collection}/post").as_str(),
257                                Some(self.clone()),
258                            )
259                            .await
260                            .and_then(|mut v| Ok(v.with_client(client.clone())))
261                    } else {
262                        client
263                            .api()
264                            .post::<Post, PostCreation>("/posts", Some(self.clone()))
265                            .await
266                            .and_then(|mut v| Ok(v.with_client(client.clone())))
267                    }
268                } else {
269                    Err(ApiError::UsageError {})
270                }
271            }
272        }
273    }
274
275    #[doc(hidden)]
276    pub mod responses {
277        use std::fmt::Debug;
278
279        use super::users::User;
280        use serde_derive::{Deserialize, Serialize};
281        use serde_json::Value;
282
283        #[derive(Clone, Debug, Serialize, Deserialize)]
284        pub struct Login {
285            pub access_token: String,
286            pub user: User,
287        }
288
289        #[derive(Clone, Debug, Serialize, Deserialize)]
290        pub struct ResponseModel {
291            pub code: u16,
292            pub data: Value,
293        }
294    }
295
296    #[doc(hidden)]
297    pub mod requests {
298        use serde_derive::{Deserialize, Serialize};
299
300        #[derive(Clone, Debug, Serialize, Deserialize)]
301        pub struct Login {
302            pub alias: String,
303            pub pass: String,
304        }
305    }
306
307    /// This module provides models related to [Collection]
308    pub mod collections {
309        use derive_builder::Builder;
310        use serde_derive::{Deserialize, Serialize};
311        use serde_repr::{Deserialize_repr, Serialize_repr};
312
313        use crate::api_client::{ApiError, Client};
314
315        use super::posts::Post;
316
317        #[derive(Clone, Debug, Serialize, Deserialize)]
318        /// A struct describing a post to move into a collection
319        pub struct MovePost {
320            /// Post ID
321            pub id: String,
322
323            /// Post token, if post isn't owned
324            pub token: Option<String>,
325        }
326
327        impl MovePost {
328            /// Creates a new MovePost with just an ID
329            pub fn new(id: &str) -> Self {
330                MovePost {
331                    id: id.to_string(),
332                    token: None,
333                }
334            }
335
336            /// Creates a new MovePost with an ID and token
337            pub fn new_with_token(id: &str, token: &str) -> Self {
338                MovePost {
339                    id: id.to_string(),
340                    token: Some(token.to_string()),
341                }
342            }
343        }
344
345        #[derive(Clone, Debug, Serialize, Deserialize)]
346        #[serde(untagged)]
347        /// Describes the result of a single post move operation
348        pub enum MoveResult {
349            /// Successful operation
350            Success { 
351                /// Operation status code
352                code: u32, 
353
354                /// Affected [Post]
355                post: Post 
356            },
357
358            /// Failed operation
359            Error { 
360                /// Operation status code
361                code: u32, 
362
363                /// Operation status text
364                error_msg: String 
365            },
366        }
367
368        #[derive(Clone, Debug, Serialize, Deserialize)]
369        /// A struct describing how to pin or unpin a post to a collection
370        pub struct PinPost {
371            /// Post ID
372            pub id: String,
373
374            #[serde(skip_serializing_if = "Option::is_none")]
375            /// Pin position (should not be used with `unpin`)
376            pub postion: Option<u64>
377        }
378
379        impl PinPost {
380            /// Creates a new PinPost with an ID
381            pub fn new(id: &str) -> Self {
382                PinPost {
383                    id: id.to_string(),
384                    postion: None
385                }
386            }
387
388            /// Creates a new PinPost with an ID and a position
389            pub fn new_at_position(id: &str, position: u64) -> Self {
390                PinPost {
391                    id: id.to_string(),
392                    postion: Some(position),
393                }
394            }
395        }
396
397        #[derive(Clone, Debug, Serialize, Deserialize)]
398        #[serde(untagged)]
399        /// Describes the result of a single pin/unpin operation
400        pub enum PinResult {
401            /// Successful operation
402            Success { 
403                /// Operation status code
404                code: u32, 
405                /// Post ID
406                id: String 
407            },
408
409            /// Failed operation
410            Error { 
411                /// Operation status code
412                code: u32, 
413                /// Operation status text
414                error_msg: String 
415            },
416        }
417
418        #[derive(Clone, Debug, Serialize, Deserialize)]
419        /// A struct describing a single Collection entity
420        pub struct Collection {
421            ///
422            pub client: Option<Client>,
423            ///
424            pub alias: String,
425            ///
426            pub title: String,
427            ///
428            pub description: Option<String>,
429            ///
430            pub style_sheet: Option<String>,
431            ///
432            pub public: bool,
433            ///
434            pub views: Option<u64>,
435            ///
436            pub verification_link: Option<String>,
437            ///
438            pub total_posts: Option<u64>,
439        }
440
441        impl Collection {
442            #[doc(hidden)]
443            pub fn with_client(&mut self, client: Client) -> Self {
444                self.client = Some(client);
445                self.clone()
446            }
447
448            /// Creates a [CollectionUpdateBuilder] with defaults set
449            pub fn build_update(&self) -> CollectionUpdateBuilder {
450                CollectionUpdateBuilder::default()
451                    .alias(Some(self.alias.clone()))
452                    .client(self.client.clone())
453                    .clone()
454            }
455            
456            /// Updates a collection from an existing [CollectionUpdate]
457            pub async fn update(&self, update: CollectionUpdate) -> Result<Collection, ApiError> {
458                if let Some(client) = self.client.clone() {
459                    client
460                        .api()
461                        .post::<Collection, CollectionUpdate>(
462                            format!("/collections/{}", self.alias).as_str(),
463                            Some(update.clone()),
464                        )
465                        .await
466                        .and_then(|mut p| Ok(p.with_client(client.clone())))
467                } else {
468                    Err(ApiError::UsageError {})
469                }
470            }
471
472            /// Deletes this [Collection]
473            pub async fn delete(&self) -> Result<(), ApiError> {
474                if let Some(client) = self.client.clone() {
475                    client
476                        .api()
477                        .delete(format!("/collections/{}", self.alias).as_str())
478                        .await
479                } else {
480                    Err(ApiError::UsageError {})
481                }
482            }
483
484            /// Returns all [Post]s belonging to this collection
485            pub async fn get_posts(&self) -> Result<Vec<Post>, ApiError> {
486                if let Some(client) = self.client.clone() {
487                    client
488                        .api()
489                        .get::<Vec<Post>>(format!("/collections/{}/posts", self.alias).as_str())
490                        .await
491                        .and_then(|mut v| {
492                            Ok(v.iter_mut()
493                                .map(|x| x.with_client(client.clone()))
494                                .collect())
495                        })
496                } else {
497                    Err(ApiError::UsageError {})
498                }
499            }
500
501            /// Returns a single [Post] belonging to this collection
502            pub async fn get_post(&self, slug: String) -> Result<Post, ApiError> {
503                if let Some(client) = self.client.clone() {
504                    client
505                        .api()
506                        .get::<Post>(format!("/collections/{}/posts/{}", self.alias, slug).as_str())
507                        .await
508                        .and_then(|mut v| Ok(v.with_client(client.clone())))
509                } else {
510                    Err(ApiError::UsageError {})
511                }
512            }
513
514            /// Moves a set of [Post]s into this collection
515            pub async fn take_posts(
516                &self,
517                posts: &[MovePost],
518            ) -> Result<Vec<Result<MoveResult, MoveResult>>, ApiError> {
519                if let Some(client) = self.client.clone() {
520                    let result = client
521                        .api()
522                        .post::<Vec<MoveResult>, &[MovePost]>(
523                            format!("/collections/{}/collect", self.alias).as_str(),
524                            Some(posts),
525                        )
526                        .await;
527                    match result {
528                        Ok(results) => Ok(results
529                            .iter()
530                            .map::<Result<MoveResult, MoveResult>, _>(|r| match r {
531                                MoveResult::Success { code, post } => Ok(MoveResult::Success {
532                                    code: code.clone(),
533                                    post: post.clone().with_client(client.clone()),
534                                }),
535                                MoveResult::Error { code, error_msg } => Err(MoveResult::Error {
536                                    code: code.clone(),
537                                    error_msg: error_msg.clone(),
538                                }),
539                            })
540                            .collect()),
541                        Err(e) => Err(e),
542                    }
543                } else {
544                    Err(ApiError::UsageError {})
545                }
546            }
547
548            /// Pins a set of [Post]s in this collection
549            pub async fn pin_posts(
550                &self,
551                posts: &[PinPost],
552            ) -> Result<Vec<Result<PinResult, PinResult>>, ApiError> {
553                if let Some(client) = self.client.clone() {
554                    let result = client
555                        .api()
556                        .post::<Vec<PinResult>, &[PinPost]>(
557                            format!("/collections/{}/pin", self.alias).as_str(),
558                            Some(posts),
559                        )
560                        .await;
561                    match result {
562                        Ok(results) => Ok(results
563                            .iter()
564                            .map::<Result<PinResult, PinResult>, _>(|r| match r {
565                                PinResult::Success { code, id } => Ok(PinResult::Success {
566                                    code: code.clone(),
567                                    id: id.clone()
568                                }),
569                                PinResult::Error { code, error_msg } => Err(PinResult::Error {
570                                    code: code.clone(),
571                                    error_msg: error_msg.clone(),
572                                }),
573                            })
574                            .collect()),
575                        Err(e) => Err(e),
576                    }
577                } else {
578                    Err(ApiError::UsageError {})
579                }
580            }
581
582            /// Unpins a set of [Post]s from this collection
583            pub async fn unpin_posts(&self, posts: &[String]) -> Result<Vec<Result<PinResult, PinResult>>, ApiError> {
584                if let Some(client) = self.client.clone() {
585                    let result = client
586                        .api()
587                        .post::<Vec<PinResult>, Vec<PinPost>>(
588                            format!("/collections/{}/unpin", self.alias).as_str(),
589                            Some(posts.iter().map(|v| PinPost::new(v.as_str())).collect()),
590                        )
591                        .await;
592                    match result {
593                        Ok(results) => Ok(results
594                            .iter()
595                            .map::<Result<PinResult, PinResult>, _>(|r| match r {
596                                PinResult::Success { code, id } => Ok(PinResult::Success {
597                                    code: code.clone(),
598                                    id: id.clone()
599                                }),
600                                PinResult::Error { code, error_msg } => Err(PinResult::Error {
601                                    code: code.clone(),
602                                    error_msg: error_msg.clone(),
603                                }),
604                            })
605                            .collect()),
606                        Err(e) => Err(e),
607                    }
608                } else {
609                    Err(ApiError::UsageError {})
610                }
611            }
612        }
613
614        #[derive(Clone, Debug, Serialize_repr, Deserialize_repr)]
615        #[repr(u8)]
616        /// Enum describing a collection's visibility
617        pub enum CollectionVisibility {
618            ///
619            Unlisted = 0,
620            ///
621            Public = 1,
622            ///
623            Private = 2,
624            ///
625            Password = 4,
626        }
627
628        #[derive(Clone, Debug, Serialize, Deserialize, Builder)]
629        /// Struct describing a collection update
630        pub struct CollectionUpdate {
631            #[serde(skip_serializing)]
632            /// [Client] instance
633            pub client: Option<Client>,
634
635            #[serde(skip_serializing)]
636            /// Collection alias to update
637            pub alias: Option<String>,
638
639            /// New title
640            pub title: Option<String>,
641
642            /// New description
643            pub description: Option<String>,
644
645            /// New style sheet
646            pub style_sheet: Option<String>,
647
648            /// New script (Write.as only)
649            pub script: Option<String>,
650
651            /// New visibility level
652            pub visibility: Option<CollectionVisibility>,
653
654            /// New password (only [CollectionVisibility::Password])
655            pub pass: Option<String>,
656
657            /// Whether to enable Mathjax support
658            pub mathjax: bool,
659        }
660
661        impl CollectionUpdate {
662            /// Publish the update request to the server
663            pub async fn update(&self) -> Result<Collection, ApiError> {
664                if let Some(client) = self.client.clone() {
665                    if let Some(alias) = self.alias.clone() {
666                        client
667                            .api()
668                            .post::<Collection, CollectionUpdate>(
669                                format!("/collections/{}", alias).as_str(),
670                                Some(self.clone()),
671                            )
672                            .await
673                            .and_then(|mut p| Ok(p.with_client(client.clone())))
674                    } else {
675                        Err(ApiError::UsageError {})
676                    }
677                } else {
678                    Err(ApiError::UsageError {})
679                }
680            }
681        }
682    }
683}