notion_into_sqlite/
notion_client.rs

1use anyhow::{anyhow, Result};
2use serde_json::{json, Value};
3
4use crate::json_util::{dig_json, JsonKey};
5use crate::notion_database::{parse_database_schema, NotionDatabaseSchema};
6use crate::notion_pages::{parse_notion_page_list, NotionPage};
7
8pub struct NotionClient {
9    pub api_key: String,
10}
11
12impl NotionClient {
13    pub fn get_database(&self, database_id: &str) -> Result<NotionDatabaseSchema> {
14        let url = format!("https://api.notion.com/v1/databases/{0}", database_id);
15        let client = reqwest::blocking::Client::new();
16        info!("Requesting database schema. URL: {}", &url);
17        let resp = client
18            .get(url)
19            .header("Authorization", "Bearer ".to_string() + &self.api_key)
20            .header("Notion-Version", "2022-02-22")
21            .send()?
22            .json::<Value>()?;
23        info!("Request done.");
24
25        self.validate_response(&resp)?;
26
27        let schema = parse_database_schema(&resp)?;
28        info!("Database schema: {:?}", schema);
29        Ok(schema)
30    }
31
32    pub fn get_all_pages(
33        &self,
34        database_id: &str,
35        schema: &NotionDatabaseSchema,
36    ) -> Result<Vec<NotionPage>> {
37        let url = format!("https://api.notion.com/v1/databases/{0}/query", database_id);
38        let client = reqwest::blocking::Client::new();
39
40        let mut next_cursor: Option<String> = None;
41        let mut all_pages: Vec<NotionPage> = vec![];
42        loop {
43            let mut query = json!({
44                "page_size": 10i32,
45                "sorts": [{
46                    "timestamp": "created_time",
47                    "direction": "ascending",
48                }]
49            });
50            if let Some(cursor) = (&next_cursor).as_ref() {
51                query
52                    .as_object_mut()
53                    .unwrap()
54                    .insert("start_cursor".into(), cursor.clone().into());
55            }
56            let query_str = query.to_string();
57
58            info!("Requesting query: URL: {}, query: {}", &url, &query_str);
59            let resp = client
60                .post(&url)
61                .header("Authorization", "Bearer ".to_string() + &self.api_key)
62                .header("Notion-Version", "2022-02-22")
63                .header("Content-Type", "application/json")
64                .body(query_str)
65                .send()?
66                .json::<Value>()?;
67            info!("Request done.");
68
69            self.validate_response(&resp)?;
70
71            let (mut pages, _next_cursor) = parse_notion_page_list(schema, &resp)?;
72            info!("Pages: {:?}", pages.len());
73            all_pages.append(&mut pages);
74            next_cursor = _next_cursor;
75
76            if next_cursor.is_none() {
77                info!("Fetched all items.");
78                break;
79            } else {
80                info!("Has more items.");
81            }
82        }
83
84        Ok(all_pages)
85    }
86
87    fn validate_response(&self, resp: &Value) -> Result<()> {
88        let json_keys = vec![JsonKey::String("object")];
89        let object_field = dig_json(resp, &json_keys)
90            .and_then(|o| o.as_str())
91            .ok_or_else(|| anyhow!("Unexpected response from Notion API: {}", resp))?;
92
93        if object_field == "error" {
94            Err(anyhow!("Error response from Notion API: {}", resp,))
95        } else {
96            Ok(())
97        }
98    }
99}