From afbb2e059e522b8e7957ce7818096d5c34aa3b68 Mon Sep 17 00:00:00 2001 From: David Weis Date: Sun, 26 Mar 2023 03:06:01 +0100 Subject: [PATCH 1/9] Fix pagination to work for pages --- src/models/mod.rs | 2 +- src/models/search.rs | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/models/mod.rs b/src/models/mod.rs index 795accd..40c6ef9 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -182,7 +182,7 @@ impl Properties { } } -#[derive(Serialize, Debug, Eq, PartialEq)] +#[derive(Serialize, Debug, Eq, PartialEq, Clone)] pub struct PageCreateRequest { pub parent: Parent, pub properties: Properties, diff --git a/src/models/search.rs b/src/models/search.rs index c9e7e91..aaf373e 100644 --- a/src/models/search.rs +++ b/src/models/search.rs @@ -44,7 +44,7 @@ pub struct Filter { value: FilterValue, } -#[derive(Serialize, Debug, Eq, PartialEq, Default)] +#[derive(Serialize, Debug, Eq, PartialEq, Default, Clone)] pub struct SearchRequest { #[serde(skip_serializing_if = "Option::is_none")] query: Option, @@ -56,6 +56,21 @@ pub struct SearchRequest { paging: Option, } +impl Pageable for SearchRequest { + fn start_from( + self, + starting_point: Option, + ) -> Self { + SearchRequest { + paging: Some(Paging { + start_cursor: starting_point, + page_size: self.paging.and_then(|p| p.page_size), + }), + ..self + } + } +} + #[derive(Serialize, Debug, Eq, PartialEq, Clone)] #[serde(rename_all = "snake_case")] pub enum TextCondition { @@ -318,7 +333,7 @@ impl Pageable for DatabaseQuery { } } -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug, Eq, PartialEq, Clone)] pub enum NotionSearch { /// When supplied, limits which pages are returned by comparing the query to the page title. Query(String), From 000203d9ba5c713fc7929caba8adf865a99ea50d Mon Sep 17 00:00:00 2001 From: David Weis Date: Sun, 26 Mar 2023 03:40:44 +0100 Subject: [PATCH 2/9] Add page update method --- src/lib.rs | 29 ++++++++++++++++++++++++++++- src/models/mod.rs | 11 ++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8ad2c24..9ca1747 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,7 @@ use crate::models::search::{DatabaseQuery, SearchRequest}; use crate::models::{Database, ListResponse, Object, Page}; use ids::{AsIdentifier, PageId}; use models::block::Block; -use models::PageCreateRequest; +use models::{PageCreateRequest, PageUpdateRequest}; use reqwest::header::{HeaderMap, HeaderValue}; use reqwest::{header, Client, ClientBuilder, RequestBuilder}; use tracing::Instrument; @@ -199,6 +199,33 @@ impl NotionApi { } } + /// Updates a page and return the updated page + pub async fn update_page( + &self, + page_id: P, + page: T, + ) -> Result + where + P: AsIdentifier, + T: Into, + { + let result = self + .make_json_request( + self.client + .patch(&format!( + "https://api.notion.com/v1/pages/{page_id}", + page_id = page_id.as_id() + )) + .json(&page.into()), + ) + .await?; + + match result { + Object::Page { page } => Ok(page), + response => Err(Error::UnexpectedResponse { response }), + } + } + /// Query a database and return the matching pages. pub async fn query_database( &self, diff --git a/src/models/mod.rs b/src/models/mod.rs index 40c6ef9..640f985 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -15,7 +15,7 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use crate::ids::{AsIdentifier, DatabaseId, PageId}; -use crate::models::block::{Block, CreateBlock}; +use crate::models::block::{Block, CreateBlock, FileOrEmojiObject}; use crate::models::error::ErrorResponse; use crate::models::paging::PagingCursor; use crate::models::users::User; @@ -190,6 +190,15 @@ pub struct PageCreateRequest { pub children: Option>, } +#[derive(Serialize, Debug, Eq, PartialEq)] +pub struct PageUpdateRequest { + pub properties: Properties, + #[serde(skip_serializing_if = "Option::is_none")] + pub archived: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub icon: Option, +} + #[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] pub struct Page { pub id: PageId, From 486cbdf9f45093c8309b3847a563a2d8a71e7d4b Mon Sep 17 00:00:00 2001 From: David Weis Date: Sun, 26 Mar 2023 03:41:06 +0100 Subject: [PATCH 3/9] Make optional fields hidden on serialisation --- src/models/text.rs | 7 +++++++ src/models/users.rs | 2 ++ 2 files changed, 9 insertions(+) diff --git a/src/models/text.rs b/src/models/text.rs index 841e7d7..832e5c8 100644 --- a/src/models/text.rs +++ b/src/models/text.rs @@ -31,11 +31,17 @@ pub enum TextColor { /// See #[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] pub struct Annotations { + #[serde(skip_serializing_if = "Option::is_none")] pub bold: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub code: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub color: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub italic: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub strikethrough: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub underline: Option, } @@ -58,6 +64,7 @@ pub struct Link { #[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] pub struct Text { pub content: String, + #[serde(skip_serializing_if = "Option::is_none")] pub link: Option, } diff --git a/src/models/users.rs b/src/models/users.rs index b7702d0..8bc3337 100644 --- a/src/models/users.rs +++ b/src/models/users.rs @@ -4,7 +4,9 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] pub struct UserCommon { pub id: UserId, + #[serde(skip_serializing_if = "Option::is_none")] pub name: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub avatar_url: Option, } From df430aef1fba85c4659219d9e023e52587f84fec Mon Sep 17 00:00:00 2001 From: David Weis Date: Sun, 26 Mar 2023 04:14:51 +0100 Subject: [PATCH 4/9] Add append block children method --- src/lib.rs | 28 +++++++++++++++++++++++++++- src/models/mod.rs | 5 +++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 9ca1747..245edfe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,7 @@ use crate::models::search::{DatabaseQuery, SearchRequest}; use crate::models::{Database, ListResponse, Object, Page}; use ids::{AsIdentifier, PageId}; use models::block::Block; -use models::{PageCreateRequest, PageUpdateRequest}; +use models::{PageCreateRequest, PageUpdateRequest, UpdateBlockChildrenRequest}; use reqwest::header::{HeaderMap, HeaderValue}; use reqwest::{header, Client, ClientBuilder, RequestBuilder}; use tracing::Instrument; @@ -268,4 +268,30 @@ impl NotionApi { response => Err(Error::UnexpectedResponse { response }), } } + + pub async fn append_block_children( + &self, + block_id: P, + request: T, + ) -> Result, Error> + where + P: AsIdentifier, + T: Into, + { + let result = self + .make_json_request( + self.client + .patch(&format!( + "https://api.notion.com/v1/blocks/{block_id}/children", + block_id = block_id.as_id() + )) + .json(&request.into()), + ) + .await?; + + match result { + Object::List { list } => Ok(list.expect_blocks()?), + response => Err(Error::UnexpectedResponse { response }), + } + } } diff --git a/src/models/mod.rs b/src/models/mod.rs index 640f985..53b322c 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -190,6 +190,11 @@ pub struct PageCreateRequest { pub children: Option>, } +#[derive(Serialize, Debug, Eq, PartialEq, Clone)] +pub struct UpdateBlockChildrenRequest { + pub children: Vec, +} + #[derive(Serialize, Debug, Eq, PartialEq)] pub struct PageUpdateRequest { pub properties: Properties, From 9d891076528b613ff3b94fd5c77a1ad5f190fbaa Mon Sep 17 00:00:00 2001 From: David Weis Date: Sun, 26 Mar 2023 04:22:34 +0100 Subject: [PATCH 5/9] Add delete block api --- src/lib.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 245edfe..86faf8f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -294,4 +294,21 @@ impl NotionApi { response => Err(Error::UnexpectedResponse { response }), } } + + pub async fn delete_block>( + &self, + block_id: T, + ) -> Result { + let result = self + .make_json_request(self.client.delete(&format!( + "https://api.notion.com/v1/blocks/{block_id}", + block_id = block_id.as_id() + ))) + .await?; + + match result { + Object::Block { block } => Ok(block), + response => Err(Error::UnexpectedResponse { response }), + } + } } From 6d6265150e94ba9024692fb25c3525f13ad532ea Mon Sep 17 00:00:00 2001 From: David Weis Date: Sun, 26 Mar 2023 05:00:13 +0100 Subject: [PATCH 6/9] Add method for getting block --- src/lib.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 86faf8f..e19da95 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -252,6 +252,25 @@ impl NotionApi { } } + /// Get a block by [BlockId]. + pub async fn get_block>( + &self, + page_id: T, + ) -> Result { + let result = self + .make_json_request(self.client.get(format!( + "https://api.notion.com/v1/blocks/{}", + page_id.as_id() + ))) + .await?; + + match result { + Object::Block { block } => Ok(block), + response => Err(Error::UnexpectedResponse { response }), + } + } + + /// Get block children a block by [BlockId]. pub async fn get_block_children>( &self, block_id: T, @@ -269,6 +288,7 @@ impl NotionApi { } } + /// Append block children under a block by [BlockId]. pub async fn append_block_children( &self, block_id: P, @@ -295,6 +315,7 @@ impl NotionApi { } } + /// Delete a block by [BlockId]. pub async fn delete_block>( &self, block_id: T, From 5eb9dd9610c19df9dee4501bb1eaa95330486a88 Mon Sep 17 00:00:00 2001 From: David Weis Date: Sun, 26 Mar 2023 05:29:53 +0100 Subject: [PATCH 7/9] Add get blocks pagination query --- src/lib.rs | 24 ++++++++++++++++++++++++ src/models/paging.rs | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index e19da95..283a8d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ use crate::models::search::{DatabaseQuery, SearchRequest}; use crate::models::{Database, ListResponse, Object, Page}; use ids::{AsIdentifier, PageId}; use models::block::Block; +use models::paging::PagingCursor; use models::{PageCreateRequest, PageUpdateRequest, UpdateBlockChildrenRequest}; use reqwest::header::{HeaderMap, HeaderValue}; use reqwest::{header, Client, ClientBuilder, RequestBuilder}; @@ -288,6 +289,29 @@ impl NotionApi { } } + /// Get block children a block by [BlockId]. + pub async fn get_block_children_with_cursor>( + &self, + block_id: T, + cursor: PagingCursor, + ) -> Result, Error> { + let result = self + .make_json_request( + self.client + .get(&format!( + "https://api.notion.com/v1/blocks/{block_id}/children", + block_id = block_id.as_id() + )) + .query(&[("start_cursor", cursor.0)]), + ) + .await?; + + match result { + Object::List { list } => Ok(list.expect_blocks()?), + response => Err(Error::UnexpectedResponse { response }), + } + } + /// Append block children under a block by [BlockId]. pub async fn append_block_children( &self, diff --git a/src/models/paging.rs b/src/models/paging.rs index c38ae11..043ff00 100644 --- a/src/models/paging.rs +++ b/src/models/paging.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone)] #[serde(transparent)] -pub struct PagingCursor(String); +pub struct PagingCursor(pub(crate) String); #[derive(Serialize, Debug, Eq, PartialEq, Default, Clone)] pub struct Paging { From ad5674c8349837bab6a4daca46ee2e55d41bf299 Mon Sep 17 00:00:00 2001 From: David Weis Date: Sun, 26 Mar 2023 05:42:10 +0100 Subject: [PATCH 8/9] Add update block method --- src/lib.rs | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 283a8d9..fec5bd5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ use crate::models::error::ErrorResponse; use crate::models::search::{DatabaseQuery, SearchRequest}; use crate::models::{Database, ListResponse, Object, Page}; use ids::{AsIdentifier, PageId}; -use models::block::Block; +use models::block::{Block, CreateBlock}; use models::paging::PagingCursor; use models::{PageCreateRequest, PageUpdateRequest, UpdateBlockChildrenRequest}; use reqwest::header::{HeaderMap, HeaderValue}; @@ -356,4 +356,35 @@ impl NotionApi { response => Err(Error::UnexpectedResponse { response }), } } + + /// Update a block by [BlockId]. + pub async fn update_block( + &self, + block_id: P, + block: T, + ) -> Result + where + P: AsIdentifier, + T: Into, + { + // using CreateBlock is not perfect + // technically speaking you can update text on a todo block and not touch checked by not settings it + // but I don't want to create a new type for this + // or make checked optional in CreateBlock + let result = self + .make_json_request( + self.client + .patch(&format!( + "https://api.notion.com/v1/blocks/{block_id}", + block_id = block_id.as_id() + )) + .json(&block.into()), + ) + .await?; + + match result { + Object::Block { block } => Ok(block), + response => Err(Error::UnexpectedResponse { response }), + } + } } From 68c8f99f3e0504783c7b802370763a86c45dcd70 Mon Sep 17 00:00:00 2001 From: David Weis Date: Sun, 26 Mar 2023 06:06:25 +0100 Subject: [PATCH 9/9] Add default for few structs --- src/models/text.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/models/text.rs b/src/models/text.rs index 832e5c8..c2233e6 100644 --- a/src/models/text.rs +++ b/src/models/text.rs @@ -29,7 +29,7 @@ pub enum TextColor { /// Rich text annotations /// See -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Default)] pub struct Annotations { #[serde(skip_serializing_if = "Option::is_none")] pub bold: Option, @@ -47,7 +47,7 @@ pub struct Annotations { /// Properties common on all rich text objects /// See -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Default)] pub struct RichTextCommon { pub plain_text: String, #[serde(skip_serializing_if = "Option::is_none")] @@ -61,7 +61,7 @@ pub struct Link { pub url: String, } -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Default)] pub struct Text { pub content: String, #[serde(skip_serializing_if = "Option::is_none")]