]> BookStack Code Mirror - api-scripts/blob - rust-list-all-pages/src/main.rs
Added rust-list-all-pages example
[api-scripts] / rust-list-all-pages / src / main.rs
1 extern crate core;
2
3 use serde::{Deserialize, Serialize};
4 use std::thread::sleep;
5 use std::time::Duration;
6 use std::{env, process};
7
8 fn main() {
9     // Gather and validate our credentials
10     let credentials = ApiCredentials::from_env();
11     match credentials.validate() {
12         Ok(()) => (),
13         Err(issues) => {
14             for issue in issues {
15                 eprintln!("Error found in API credentials: {}", issue)
16             }
17             process::exit(1)
18         }
19     }
20
21     // Get all our pages from the BookStack API and list them out
22     let pages = get_all_pages(credentials);
23     for page in pages {
24         println!("ID={}; Name={}", page.id, page.name);
25     }
26 }
27
28 // Get all pages from the BookStack instance
29 fn get_all_pages(credentials: ApiCredentials) -> Vec<BookStackPage> {
30     // Create a new vector of pages to contain results
31     let mut results: Vec<BookStackPage> = Vec::new();
32
33     // Variables to track and perform API endpoint paging
34     let mut total = 100;
35     let mut offset = 0;
36     let page_size = 500;
37
38     // Make API requests while there's still data to retrieve
39     while offset < total {
40         // Delay to respect rate limits
41         if offset > 0 {
42             sleep(Duration::from_millis(500));
43         }
44
45         let response: BookStackApiListingResult<BookStackPage> =
46             get_page_of_pages(&credentials, page_size, offset);
47
48         // Update our paging variables
49         total = response.total;
50         offset += page_size;
51
52         // Append the fetched data to our results
53         results.append(&mut response.data.clone());
54     }
55
56     return results;
57 }
58
59 // Get a single listing page of "pages" from the BookStack API
60 fn get_page_of_pages(
61     credentials: &ApiCredentials,
62     page_size: i64,
63     offset: i64,
64 ) -> BookStackApiListingResult<BookStackPage> {
65     // Build our API endpoint to call
66     let endpoint = format!(
67         "{}/api/pages?count={}&offset={}",
68         credentials.url, page_size, offset
69     );
70
71     // Build the authorization header value to authenticate with the BookStack API
72     let auth_header_val = format!(
73         "Token {}:{}",
74         credentials.token_id, credentials.token_secret
75     );
76
77     // Create a client for making API requests
78     let api = reqwest::blocking::Client::new();
79
80     // Perform the API request
81     let api_result = api
82         .get(endpoint)
83         .header("Authorization", auth_header_val)
84         .send();
85
86     // Gather, parse and return our response data
87     let response: BookStackApiListingResult<BookStackPage> = api_result.unwrap().json().unwrap();
88     return response;
89 }
90
91 // A struct to represent a BookStack API listing response
92 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
93 struct BookStackApiListingResult<T> {
94     data: Vec<T>,
95     total: i64,
96 }
97
98 // A struct to represent a BookStack page provided via an API listing response
99 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
100 struct BookStackPage {
101     id: i64,
102     book_id: i64,
103     chapter_id: i64,
104     name: String,
105     slug: String,
106     priority: i64,
107     draft: bool,
108     template: bool,
109     created_at: String,
110     updated_at: String,
111     created_by: i64,
112     updated_by: i64,
113     owned_by: i64,
114 }
115
116 // A struct to hold BookStack API details
117 struct ApiCredentials {
118     url: String,
119     token_id: String,
120     token_secret: String,
121 }
122
123 impl ApiCredentials {
124     // Construct ApiCredentials by reading from possible environment variables
125     fn from_env() -> ApiCredentials {
126         let mut credentials = ApiCredentials {
127             url: String::from(""),
128             token_id: String::from(""),
129             token_secret: String::from(""),
130         };
131
132         credentials.url = match env::var("BS_URL") {
133             Ok(val) => val,
134             Err(_e) => credentials.url,
135         };
136
137         credentials.token_id = match env::var("BS_TOKEN_ID") {
138             Ok(val) => val,
139             Err(_e) => credentials.token_id,
140         };
141
142         credentials.token_secret = match env::var("BS_TOKEN_SECRET") {
143             Ok(val) => val,
144             Err(_e) => credentials.token_secret,
145         };
146
147         return credentials;
148     }
149
150     // Validate the current set API credentials
151     fn validate(&self) -> Result<(), Vec<String>> {
152         let mut issues: Vec<String> = Vec::new();
153
154         if self.url.is_empty() {
155             issues.push("API Base URL not set!".to_string());
156         }
157
158         if self.token_id.is_empty() {
159             issues.push("API Token ID not set!".to_string());
160         }
161
162         if self.token_secret.is_empty() {
163             issues.push("API Token Secret not set!".to_string());
164         }
165
166         if !issues.is_empty() {
167             return Err(issues);
168         }
169
170         Ok(())
171     }
172 }