diff --git a/src/lib.rs b/src/lib.rs index 8ad2c24..fec5bd5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,8 +3,9 @@ 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::PageCreateRequest; +use models::block::{Block, CreateBlock}; +use models::paging::PagingCursor; +use models::{PageCreateRequest, PageUpdateRequest, UpdateBlockChildrenRequest}; use reqwest::header::{HeaderMap, HeaderValue}; use reqwest::{header, Client, ClientBuilder, RequestBuilder}; use tracing::Instrument; @@ -199,6 +200,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, @@ -225,6 +253,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, @@ -241,4 +288,103 @@ impl NotionApi { response => Err(Error::UnexpectedResponse { response }), } } + + /// 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, + 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 }), + } + } + + /// Delete a block by [BlockId]. + 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 }), + } + } + + /// 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 }), + } + } } diff --git a/src/models/mod.rs b/src/models/mod.rs index 795accd..53b322c 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; @@ -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, @@ -190,6 +190,20 @@ 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, + #[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, 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 { 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), diff --git a/src/models/text.rs b/src/models/text.rs index 841e7d7..c2233e6 100644 --- a/src/models/text.rs +++ b/src/models/text.rs @@ -29,19 +29,25 @@ 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, + #[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, } /// 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")] @@ -55,9 +61,10 @@ 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")] 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, }